From f84be5662be6057c2aa20b799d149f5ccf879943 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Du=C5=A1an=20Bratu=C5=A1a?= <dusan.bratusa@gmail.com>
Date: Sat, 9 Apr 2022 16:31:48 +0200
Subject: [PATCH 1/2] Allow to dynamically specify suite classes in custom
 Categories subclass

---
 .../experimental/categories/Categories.java   | 17 +++--
 src/main/java/org/junit/runners/Suite.java    |  2 +-
 .../categories/CustomCategoriesSuiteTest.java | 66 +++++++++++++++++++
 3 files changed, 80 insertions(+), 5 deletions(-)
 create mode 100644 src/test/java/org/junit/experimental/categories/CustomCategoriesSuiteTest.java

diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java
index 0c73ed82afff..6a75010f477a 100644
--- a/src/main/java/org/junit/experimental/categories/Categories.java
+++ b/src/main/java/org/junit/experimental/categories/Categories.java
@@ -307,11 +307,20 @@ private static Set<Class<?>> copyAndRefine(Set<Class<?>> classes) {
 
     public Categories(Class<?> klass, RunnerBuilder builder) throws InitializationError {
         super(klass, builder);
+        initialize(klass);
+    }
+
+    protected Categories(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
+        super(builder, klass, suiteClasses);
+        initialize(klass);
+    }
+
+    private void initialize(Class<?> klass) throws InitializationError {
         try {
-            Set<Class<?>> included= getIncludedCategory(klass);
-            Set<Class<?>> excluded= getExcludedCategory(klass);
-            boolean isAnyIncluded= isAnyIncluded(klass);
-            boolean isAnyExcluded= isAnyExcluded(klass);
+            Set<Class<?>> included = getIncludedCategory(klass);
+            Set<Class<?>> excluded = getExcludedCategory(klass);
+            boolean isAnyIncluded = isAnyIncluded(klass);
+            boolean isAnyExcluded = isAnyExcluded(klass);
 
             filter(CategoryFilter.categoryFilter(isAnyIncluded, included, isAnyExcluded, excluded));
         } catch (NoTestsRemainException e) {
diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java
index c2c8e583ea66..79e43cb52f19 100644
--- a/src/main/java/org/junit/runners/Suite.java
+++ b/src/main/java/org/junit/runners/Suite.java
@@ -98,7 +98,7 @@ protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationEr
      * @param klass the root of the suite
      * @param suiteClasses the classes in the suite
      */
-    protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
+    public Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
         this(klass, builder.runners(klass, suiteClasses));
     }
 
diff --git a/src/test/java/org/junit/experimental/categories/CustomCategoriesSuiteTest.java b/src/test/java/org/junit/experimental/categories/CustomCategoriesSuiteTest.java
new file mode 100644
index 000000000000..542946fd935e
--- /dev/null
+++ b/src/test/java/org/junit/experimental/categories/CustomCategoriesSuiteTest.java
@@ -0,0 +1,66 @@
+package org.junit.experimental.categories;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Categories.IncludeCategory;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+public class CustomCategoriesSuiteTest {
+
+    public static class CustomCategories extends Categories {
+        public CustomCategories(Class<?> klass, RunnerBuilder builder)
+                throws InitializationError {
+            super(builder, klass, suiteClasses());
+        }
+
+        private static Class<?>[] suiteClasses() {
+            // Here a custom algorithm could be invoked to determine which
+            // classes should be run by the test suite.
+            // e.g. Reflections lib. (org.reflections) could be used to
+            // determine subclasses of a abstract test class
+            // which could be provided to the test suite by a custom annotation.
+            return new Class<?>[] { TestWithCategory.class };
+        }
+    }
+
+    public static class TestCategory {
+    }
+
+    @Category(TestCategory.class)
+    public static class TestWithCategory {
+        @Parameters
+        public static Iterable<String> getParameters() {
+            return Arrays.asList("first", "second");
+        }
+
+        @Parameterized.Parameter
+        public String value;
+
+        @Test
+        public void testSomething() {
+            Assert.assertTrue(true);
+        }
+    }
+
+    @RunWith(CustomCategories.class)
+    @IncludeCategory(TestCategory.class)
+    public static class SuiteWithTestWithCategory {
+    }
+
+    @Test
+    public void runsTestsWithCategory() {
+        Result result = new JUnitCore().run(SuiteWithTestWithCategory.class);
+        assertEquals(1, result.getRunCount());
+        assertEquals(0, result.getFailureCount());
+    }
+}

From 4f7cffc1d0a68678dcf8839c36320bac47b5421c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Du=C5=A1an=20Bratu=C5=A1a?= <dusan.bratusa@gmail.com>
Date: Sat, 9 Apr 2022 16:43:56 +0200
Subject: [PATCH 2/2] Reverted unnecessary visibility change of Suite
 constructor

---
 src/main/java/org/junit/runners/Suite.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java
index 79e43cb52f19..c2c8e583ea66 100644
--- a/src/main/java/org/junit/runners/Suite.java
+++ b/src/main/java/org/junit/runners/Suite.java
@@ -98,7 +98,7 @@ protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationEr
      * @param klass the root of the suite
      * @param suiteClasses the classes in the suite
      */
-    public Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
+    protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
         this(klass, builder.runners(klass, suiteClasses));
     }