From 1b73fcea253bce59391b9ed18a97c7879ae47f78 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 May 2026 10:40:15 -0700 Subject: [PATCH 1/4] Fix JUnit test naming to work with TeamCity and Gradle 9.3 --- src/org/labkey/test/Runner.java | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/Runner.java b/src/org/labkey/test/Runner.java index bacc2d7a6b..a44752f661 100644 --- a/src/org/labkey/test/Runner.java +++ b/src/org/labkey/test/Runner.java @@ -547,7 +547,7 @@ public String describe() } else if (test.countTestCases() > 0) { - suite.addTest(test); + flattenSuiteInto(suite, (TestSuite) test); foundServerSideTest = true; } } @@ -570,7 +570,7 @@ else if (specifiedSuite.startsWith("?") && specifiedSuite.length() > 1) } TestSuite dynamicSuite = JUnitTest.dynamicSuite(requestedSuites, excludedSuites); if (dynamicSuite.countTestCases() > 0) - suite.addTest(dynamicSuite); + flattenSuiteInto(suite, dynamicSuite); } } @@ -1126,6 +1126,26 @@ private static String getModuleNameFromPath(File path) return null; } + private static void flattenSuiteInto(TestSuite destination, TestSuite source) + { + Enumeration tests = source.tests(); + while (tests.hasMoreElements()) + { + Test t = tests.nextElement(); + if (t instanceof TestSuite nested) + { + if (TestProperties.isTestRunningOnTeamCity()) + flattenSuiteInto(destination, nested); + else if (nested.testCount() > 0) + destination.addTest(t); + } + else + { + destination.addTest(t); + } + } + } + private static Class getTestClass(Test test) { if (test instanceof JUnit4TestAdapter) From 60173d93fae649f2bf33c0b2044701ac46620afc Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 May 2026 12:24:26 -0700 Subject: [PATCH 2/4] Code cleanup in Runner.java and JUnitTest.java --- build.gradle | 3 -- src/org/labkey/test/Runner.java | 27 ++++++++++++------ src/org/labkey/test/tests/JUnitTest.java | 35 ++++++++++++------------ 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index aa002ab263..1cd4499b30 100644 --- a/build.gradle +++ b/build.gradle @@ -182,9 +182,6 @@ project.tasks.named("uiTests").configure { project.parent.parent.tasks.named("ijConfigure").configure { dependsOn(initPropertiesTask) } -project.tasks.withType(RunTestSuite).configureEach { it -> - scanForTestClasses = true // Required for Gradle 9.3 -} if (project.hasProperty('doPublishing')) { diff --git a/src/org/labkey/test/Runner.java b/src/org/labkey/test/Runner.java index a44752f661..c4d0c6fa90 100644 --- a/src/org/labkey/test/Runner.java +++ b/src/org/labkey/test/Runner.java @@ -203,7 +203,7 @@ private static Class[] readClasses(File recentlyFailedTestsFile, List currentTestClass = getTestClass(test); - final String currentTestName = currentTestClass.getSimpleName(); + final String currentTestName = getTestName(test); TestListener classFailListener = new TestListener() { @@ -303,8 +302,8 @@ public void endTest(Test _test) { } // This stub matches the failure generated by JUnit when it fails during static setup/teardown (e.g. @BeforeClass) // Without this TeamCity has no way of knowing when a setup/teardown failure has been resolved - final Test loggingStub = test instanceof JUnit4TestAdapter ? - new TestSuite(currentTestClass) : + final Test loggingStub = test instanceof JUnit4TestAdapter jUnit4TestAdapter ? + new TestSuite(jUnit4TestAdapter.getTestClass()) : null; if (loggingStub != null) @@ -1021,7 +1020,7 @@ else if (testNames.isEmpty()) Test test = e.nextElement(); Class testClass = getTestClass(test); _remainingTests.add(testClass); - LOG.info(" " + testClass.getSimpleName()); + LOG.info(" " + getTestName(test)); for (String testMethod : specifiedTestMethods.getOrDefault(testClass, Collections.emptyList())) { LOG.info(" ." + testMethod); @@ -1146,10 +1145,22 @@ else if (nested.testCount() > 0) } } + private static String getTestName(Test test) + { + if (test instanceof JUnit4TestAdapter testAdapter) + return testAdapter.getTestClass().getName(); + else if (test instanceof JUnitTest.RemoteTest remoteTest) + return remoteTest.getName(); + else + return test.getClass().getSimpleName(); + } + private static Class getTestClass(Test test) { - if (test instanceof JUnit4TestAdapter) - return ((JUnit4TestAdapter) test).getTestClass(); + if (test instanceof JUnit4TestAdapter testAdapter) + return testAdapter.getTestClass(); + else if (test instanceof JUnitTest.RemoteTest) + return JUnitTest.class; else return test.getClass(); } diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index 2b785e1fee..8016ef095d 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -84,7 +84,7 @@ public JUnitTest() public static TestSuite suite() throws Exception { - return JUnitTest._suite((p) -> true, false); + return JUnitTest._suite(_ -> true, false); } private static String getWhen(Map test) @@ -136,12 +136,9 @@ private static void upgradeHelper(boolean skipInitialUserChecks) } catch (Throwable t) { - if (bootstrapBrowser.getWrappedDriver() != null) - { - ArtifactCollector artifactCollector = new ArtifactCollector(bootstrapBrowser, JUnitTest.class.getSimpleName()); - artifactCollector.dumpPageSnapshot("ServerBootstrap", null); - artifactCollector.publishDumpedArtifacts(); - } + ArtifactCollector artifactCollector = new ArtifactCollector(bootstrapBrowser, JUnitTest.class.getSimpleName()); + artifactCollector.dumpPageSnapshot("ServerBootstrap", null); + artifactCollector.publishDumpedArtifacts(); throw t; } finally @@ -176,8 +173,7 @@ public static TestSuite dynamicSuite(Collection categories, CollectionUpgrade Status") || catch (Throwable t) { upgradeError = t; - t.printStackTrace(); + LOG.warn("Error during upgrade/bootstrap", t); } TestSuite testSuite; try @@ -298,7 +294,7 @@ else if (responseBody.contains("Upgrade Status") || } catch (Exception retryException) { - retryException.printStackTrace(); + LOG.warn("Error fetching remote test suite", retryException); testSuite = new TestSuite(); testSuite.addTest(new Runner.ErrorTest("", retryException)); } @@ -321,7 +317,7 @@ else if (responseBody.contains("Upgrade Status") || for (String key : json.keySet()) { AtomicInteger ioeCounter = new AtomicInteger(0); - TestSuite testsuite = new TestSuite(key); + TestSuite moduleSuite = new TestSuite(key + " Tests"); JSONArray testClassArray = json.getJSONArray(key); // Individual tests include both the class name and the requested timeout for (int i = 0; i < testClassArray.length(); i++) @@ -334,17 +330,20 @@ else if (responseBody.contains("Upgrade Status") || // Timeout is represented in seconds int timeout = testClass.getInt("timeout"); if (accept.test(testClass.toMap())) - testsuite.addTest(new RemoteTest(className, timeout, ioeCounter)); + moduleSuite.addTest(new RemoteTest(className, timeout, ioeCounter)); } } - if (!addedHeader && testsuite.countTestCases() > 0) + if (moduleSuite.countTestCases() > 0) { - BaseJUnitTestWrapper.extraSetup = !skipInitialUserChecks; - remotesuite.addTest(new JUnit4TestAdapter(JUnitHeader.class)); - addedHeader = true; + if (!addedHeader) + { + BaseJUnitTestWrapper.extraSetup = !skipInitialUserChecks; + remotesuite.addTest(new JUnit4TestAdapter(JUnitHeader.class)); + addedHeader = true; + } + remotesuite.addTest(moduleSuite); } - remotesuite.addTest(testsuite); } if (addedHeader) { From 41616f8d7d9d3f4a560eba92875c8df7c59b25a1 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 May 2026 13:57:41 -0700 Subject: [PATCH 3/4] Improve test logging for Gradle 9.3 compatibility --- src/org/labkey/test/Runner.java | 90 ++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/org/labkey/test/Runner.java b/src/org/labkey/test/Runner.java index c4d0c6fa90..b9789bde8a 100644 --- a/src/org/labkey/test/Runner.java +++ b/src/org/labkey/test/Runner.java @@ -129,21 +129,24 @@ private void updateRemainingTests(Test test, boolean failed, boolean errored) Class testClass = getTestClass(test); _remainingTests.remove(testClass); if (failed) - _failedTests.add(test.toString()); + _failedTests.add(getTestName(test)); else if (errored) - _erroredTests.add(test.toString()); + _erroredTests.add(getTestName(test)); else - _passedTests.add(test.toString()); + _passedTests.add(getTestName(test)); } private static void writeRemainingTests() { - ArrayList failedAndRemaining = new ArrayList<>(); - failedAndRemaining.addAll(_failedTests); - failedAndRemaining.addAll(_erroredTests); - for (Class clazz : _remainingTests) - failedAndRemaining.add(clazz.getName()); - writeClasses(failedAndRemaining, getRemainingTestsFile()); + if (!TestProperties.isTestRunningOnTeamCity()) // Not useful on TeamCity + { + ArrayList failedAndRemaining = new ArrayList<>(); + failedAndRemaining.addAll(_failedTests); + failedAndRemaining.addAll(_erroredTests); + for (Class clazz : _remainingTests) + failedAndRemaining.add(clazz.getName()); + writeClasses(failedAndRemaining, getRemainingTestsFile()); + } } private static void writeClasses(List tests, File file) @@ -297,8 +300,11 @@ public void endTest(Test _test) { } }; testResult.addListener(classFailListener); - logToServer("=== Starting " + currentTestName + getProgress() + " ==="); - LOG.info("=============== Starting " + currentTestName + getProgress() + " ================="); + if (!(test instanceof JUnitTest.RemoteTest)) + { + logToServer("=== Starting " + currentTestName + getProgress() + " ==="); + LOG.info("=============== Starting " + currentTestName + getProgress() + " ================="); + } // This stub matches the failure generated by JUnit when it fails during static setup/teardown (e.g. @BeforeClass) // Without this TeamCity has no way of knowing when a setup/teardown failure has been resolved @@ -316,10 +322,13 @@ public void endTest(Test _test) { } testResult.removeListener(classFailListener); - String result = failed.booleanValue() || errored.booleanValue() ? "Failed " : "Completed "; - TestLogger.resetLogger(); - LOG.info("=============== " + result + currentTestName + getProgress() + " ================="); - logToServer("=== " + result + currentTestName + getProgress() + " ==="); + if (!(test instanceof JUnitTest.RemoteTest)) + { + String result = failed.booleanValue() || errored.booleanValue() ? "Failed " : "Completed "; + TestLogger.resetLogger(); + LOG.info("=============== " + result + currentTestName + getProgress() + " ================="); + logToServer("=== " + result + currentTestName + getProgress() + " ==="); + } } else @@ -602,9 +611,9 @@ public void run(TestResult testResult) private static void writeTimeReport() { - int width = 60; + int width = 64; long total = 0; - LOG.info("======================= Time Report ========================"); + LOG.info(getCenteredText("Time Report", '=', width)); Duration totalCrawlTime = Duration.ZERO; int totalUniquePages = 0; @@ -618,7 +627,7 @@ private static void writeTimeReport() } for (Map.Entry entry : _testStats.entrySet()) { - String testName = entry.getKey().toString(); + String testName = getTestName(entry.getKey()); long duration = entry.getValue(); long percent = Math.round(100.0 * (duration / (double) total)); @@ -629,7 +638,6 @@ private static void writeTimeReport() (_erroredTests.contains(testName) ? "ERROR" : "not run"))) + " - " + formatDuration(duration) + " " + percentStr; - testName = testName.substring(testName.lastIndexOf('.') + 1); LOG.info(getFixedWidthString(testName, durationAndPercent, width)); @@ -685,7 +693,7 @@ private static void writeTimeReport() } if (!TeamCityUtils.getBuildStatistics().isEmpty()) { - LOG.info("--------------------- Build Statistics ---------------------"); + LOG.info(getCenteredText("Build Statistics", '-', width)); for (String stat : TeamCityUtils.getBuildStatistics().keySet()) { List values = TeamCityUtils.getBuildStatistics().get(stat); @@ -701,7 +709,7 @@ private static void writeTimeReport() Map> actionWarnings = WebDriverWrapper.getActionWarnings(); if (!actionWarnings.isEmpty()) { - LOG.info("---------------------- Test Warnings -----------------------"); + LOG.info(getCenteredText("Test Warnings", '-', width)); for (String warning : actionWarnings.keySet()) { LOG.info(" " + warning + ":"); @@ -713,7 +721,7 @@ private static void writeTimeReport() } } } - LOG.info("------------------------------------------------------------"); + LOG.info("-".repeat(width)); LOG.info(getFixedWidthString("Total duration:", formatDuration(total), width) + "\n"); LOG.info("Completed " + FastDateFormat.getInstance("yyyy-MM-dd HH:mm").format(new Date())); } @@ -743,10 +751,23 @@ private static String getRowString(String[] list, int columnWidth) private static String getFixedWidthString(String prefix, String suffix, int length) { int contentLength = prefix.length() + suffix.length(); - int padding = Math.max(0, length - contentLength); + int padding = Math.max(1, length - contentLength); return prefix + " ".repeat(padding) + suffix; } + private static String getCenteredText(String text, char paddingChar, int width) + { + text = StringUtils.trimToEmpty(text); + if (text.length() > width) + return text; + + text = text.isEmpty() ? "" : " " + text.trim() + " "; + int paddingLength = width - text.length(); + int leftPadding = paddingLength / 2; + int rightPadding = paddingLength - leftPadding; + return Character.toString(paddingChar).repeat(leftPadding) + text + Character.toString(paddingChar).repeat(rightPadding); + } + private static TestSet getCompositeTestSet(List suitesColl) { if (suitesColl.isEmpty()) @@ -1127,28 +1148,29 @@ private static String getModuleNameFromPath(File path) private static void flattenSuiteInto(TestSuite destination, TestSuite source) { - Enumeration tests = source.tests(); - while (tests.hasMoreElements()) + if (TestProperties.isTestRunningOnTeamCity()) { - Test t = tests.nextElement(); - if (t instanceof TestSuite nested) + // Flatten test suites so that TeamCity will correlate with results from before Gradle 9.3 + Enumeration tests = source.tests(); + while (tests.hasMoreElements()) { - if (TestProperties.isTestRunningOnTeamCity()) + Test t = tests.nextElement(); + if (t instanceof TestSuite nested) flattenSuiteInto(destination, nested); - else if (nested.testCount() > 0) + else destination.addTest(t); } - else - { - destination.addTest(t); - } + } + else + { + destination.addTest(source); } } private static String getTestName(Test test) { if (test instanceof JUnit4TestAdapter testAdapter) - return testAdapter.getTestClass().getName(); + return testAdapter.getTestClass().getSimpleName(); else if (test instanceof JUnitTest.RemoteTest remoteTest) return remoteTest.getName(); else From 0ac0fc0fca3b962be3ac956f98479bb1cfc9026c Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 May 2026 14:09:15 -0700 Subject: [PATCH 4/4] Remove redundant trim --- src/org/labkey/test/Runner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/Runner.java b/src/org/labkey/test/Runner.java index b9789bde8a..39826754ba 100644 --- a/src/org/labkey/test/Runner.java +++ b/src/org/labkey/test/Runner.java @@ -761,7 +761,7 @@ private static String getCenteredText(String text, char paddingChar, int width) if (text.length() > width) return text; - text = text.isEmpty() ? "" : " " + text.trim() + " "; + text = text.isEmpty() ? "" : " " + text + " "; int paddingLength = width - text.length(); int leftPadding = paddingLength / 2; int rightPadding = paddingLength - leftPadding;