From 6c0578098778ddb5378f7a4487da19736c714b30 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 00:38:17 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20Replace=20Regex=20prefix/suffix=20matching=20with=20Stri?= =?UTF-8?q?ng=20startsWith/endsWith?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced `replaceFirst` with regex patterns (`^prefix\.` and `\.suffix$`) with `startsWith()`, `endsWith()`, and `substring()` across `ClassNameEvaluation.java` and `BaseTools.java`. This optimization eliminates regex compilation and evaluation overhead during test class resolution. Trailing/leading dot requirements were preserved exactly as defined by the original regex. Co-authored-by: RoiSoleil <3462260+RoiSoleil@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../moreunit/matching/ClassNameEvaluation.java | 12 ++++++++++-- .../src/org/moreunit/util/BaseTools.java | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 21e51091..425e0f3f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -15,3 +15,6 @@ ## 2026-05-19 - Replacing regex with indexOf for simple template token parsing **Learning:** For resolving code templates with simple prefix/suffix patterns like `${:setDependency(foo)}` where the exact pattern is known but inner content is variable, compiling and executing regex patterns via `Matcher.replaceAll()` is relatively slow compared to manual string parsing. Benchmarks showed that swapping `Matcher.replaceAll` out for `indexOf` and `substring` concatenation resulted in an approximate 5x speedup (~810ms vs ~170ms for 1M iterations) in parsing strings within `SimplePatternResolver` subclasses. Furthermore, removing regex replacement naturally eliminated issues with regex engines incorrectly treating `$` and `{` inside literal replacement strings as capturing groups or illegal sequences. **Action:** Identify and replace usages of regex matching (`Pattern`, `Matcher`) with literal string searches (`indexOf`, `substring`) when extracting and replacing localized, simple bounded template strings within hot paths. +## 2024-05-30 - Replace regex replaceFirst with String startsWith/endsWith combined with substring +**Learning:** Using regex-based `String.replaceFirst()` for prefix and suffix removal is computationally expensive due to Pattern compilation and execution overhead. Replacing this pattern with `String.startsWith()` or `String.endsWith()` combined with `String.substring()` yields a significant performance speedup and prevents syntax exception bugs. However, one must be careful to preserve the original behavior of `replaceFirst` if the string exactly equals the prefix/suffix. If `replaceFirst("^prefix\\.", "")` was used, it would ONLY remove the prefix if there is a trailing dot. If `packageName.equals("prefix")`, `replaceFirst` would not match and `packageName` remains `"prefix"`. My previous implementation incorrectly set it to `""`. +**Action:** Always favor `String.startsWith`/`endsWith` + `String.substring` over `String.replaceFirst` for literal prefix/suffix removal. Ensure the logic exactly matches the original regex (e.g. trailing dots matching). diff --git a/org.moreunit.plugin/src/org/moreunit/matching/ClassNameEvaluation.java b/org.moreunit.plugin/src/org/moreunit/matching/ClassNameEvaluation.java index 3cec194f..95ff07ba 100644 --- a/org.moreunit.plugin/src/org/moreunit/matching/ClassNameEvaluation.java +++ b/org.moreunit.plugin/src/org/moreunit/matching/ClassNameEvaluation.java @@ -54,12 +54,20 @@ private String getCutPackageName() if(packagePrefix != null) { - packageName = packageName.replaceFirst("^" + packagePrefix + "\\.", ""); + String prefixWithDot = packagePrefix + "."; + if(packageName.startsWith(prefixWithDot)) + { + packageName = packageName.substring(prefixWithDot.length()); + } } if(packageSuffix != null) { - packageName = packageName.replaceFirst("\\." + packageSuffix + "$", ""); + String dotWithSuffix = "." + packageSuffix; + if(packageName.endsWith(dotWithSuffix)) + { + packageName = packageName.substring(0, packageName.length() - dotWithSuffix.length()); + } } return packageName; diff --git a/org.moreunit.plugin/src/org/moreunit/util/BaseTools.java b/org.moreunit.plugin/src/org/moreunit/util/BaseTools.java index e194fe9d..fa0ae539 100644 --- a/org.moreunit.plugin/src/org/moreunit/util/BaseTools.java +++ b/org.moreunit.plugin/src/org/moreunit/util/BaseTools.java @@ -41,11 +41,23 @@ public static List getTestedClass(String testCaseClass, String[] prefixe { if(packagePrefix != null && packagePrefix.length() > 0) { - packagePath = packagePath.replaceFirst("^" + packagePrefix + "\\.", ""); + String prefixWithDot = packagePrefix + "."; + if(packagePath.startsWith(prefixWithDot)) + { + packagePath = packagePath.substring(prefixWithDot.length()); + } } if(packageSuffix != null && packageSuffix.length() > 0) { - packagePath = packagePath.replaceFirst("\\b" + packageSuffix + "\\.$", ""); + String dotWithSuffixWithDot = "." + packageSuffix + "."; + if(packagePath.endsWith(dotWithSuffixWithDot)) + { + packagePath = packagePath.substring(0, packagePath.length() - dotWithSuffixWithDot.length() + 1); + } + else if(packagePath.equals(packageSuffix + ".")) + { + packagePath = ""; + } } } @@ -69,7 +81,7 @@ public static List getTestedClass(String testCaseClass, String[] prefixe { if(typeName.startsWith(prefix)) { - results.add(packagePath + typeName.replaceFirst(prefix, "")); + results.add(packagePath + typeName.substring(prefix.length())); } } } From e00fc405f06b5464113de10625d70aa887b09fcd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 01:23:12 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20Replace=20Regex=20prefix/suffix=20matching=20with=20Stri?= =?UTF-8?q?ng=20startsWith/endsWith=20&=20Increase=20SWTBot=20Timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced replaceFirst with regex patterns with startsWith(), endsWith(), and substring() across ClassNameEvaluation.java and BaseTools.java. This optimization eliminates regex compilation and evaluation overhead during test class resolution. Trailing/leading dot requirements were preserved exactly as defined by the original regex. Increased SWTBot test timeout to 20000ms to resolve CI flakiness in GitHub actions. Co-authored-by: RoiSoleil <3462260+RoiSoleil@users.noreply.github.com> --- .../src/org/moreunit/JavaProjectSWTBotTestHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.moreunit.swtbot.test/src/org/moreunit/JavaProjectSWTBotTestHelper.java b/org.moreunit.swtbot.test/src/org/moreunit/JavaProjectSWTBotTestHelper.java index 7b95ebcb..81bf5ab4 100644 --- a/org.moreunit.swtbot.test/src/org/moreunit/JavaProjectSWTBotTestHelper.java +++ b/org.moreunit.swtbot.test/src/org/moreunit/JavaProjectSWTBotTestHelper.java @@ -63,6 +63,7 @@ public static void initialize() if(! isWorkspacePrepared) { // SWTBotPreferences.PLAYBACK_DELAY = 10; + SWTBotPreferences.TIMEOUT = 20000; // not all keyboard layouts are supported by SWTBot // e.g. MAC_DE is missing (see http://wiki.eclipse.org/SWTBot/Keyboard_Layouts) From 5fbcf9d9ea1103c09f12bbca07044d5e56fe3f50 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 02:16:39 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20Replace=20Regex=20prefix/suffix=20matching=20with=20Stri?= =?UTF-8?q?ng=20startsWith/endsWith=20&=20Increase=20SWTBot=20Timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced replaceFirst with regex patterns with startsWith(), endsWith(), and substring() across ClassNameEvaluation.java and BaseTools.java. This optimization eliminates regex compilation and evaluation overhead during test class resolution. Trailing/leading dot requirements were preserved exactly as defined by the original regex. Increased SWTBot test timeout to 20000ms to resolve CI flakiness in GitHub actions. Added missing test coverage for base package packageSuffix edge-case evaluation to satisfy codecov checks. Co-authored-by: RoiSoleil <3462260+RoiSoleil@users.noreply.github.com> --- .jules/bolt.md | 6 ++ .../matching/ClassNameEvaluationTest.java | 67 +++++++++++++++++++ .../test/org/moreunit/util/BaseToolsTest.java | 3 + 3 files changed, 76 insertions(+) create mode 100644 org.moreunit.test/test/org/moreunit/matching/ClassNameEvaluationTest.java diff --git a/.jules/bolt.md b/.jules/bolt.md index 425e0f3f..9a655c15 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -18,3 +18,9 @@ ## 2024-05-30 - Replace regex replaceFirst with String startsWith/endsWith combined with substring **Learning:** Using regex-based `String.replaceFirst()` for prefix and suffix removal is computationally expensive due to Pattern compilation and execution overhead. Replacing this pattern with `String.startsWith()` or `String.endsWith()` combined with `String.substring()` yields a significant performance speedup and prevents syntax exception bugs. However, one must be careful to preserve the original behavior of `replaceFirst` if the string exactly equals the prefix/suffix. If `replaceFirst("^prefix\\.", "")` was used, it would ONLY remove the prefix if there is a trailing dot. If `packageName.equals("prefix")`, `replaceFirst` would not match and `packageName` remains `"prefix"`. My previous implementation incorrectly set it to `""`. **Action:** Always favor `String.startsWith`/`endsWith` + `String.substring` over `String.replaceFirst` for literal prefix/suffix removal. Ensure the logic exactly matches the original regex (e.g. trailing dots matching). +## 2026-05-21 - SWTBot UI Race Conditions +**Learning:** `RenameMethodTest` and other SWTBot tests occasionally fail due to race conditions. Specifically, Eclipse refactorings run as background `Job`s. `bot.waitUntil(Conditions.shellCloses(renameDialog))` confirms the UI dialog closed, but does *not* guarantee the background Java model update is complete before the test asserts the method name, leading to `element(s) not found` errors. +**Action:** When investigating CI test failures, identify if the failure stems from missing wait conditions for background jobs in headless environments rather than logical code regressions. +## 2026-05-21 - Code Coverage Reductions from Edge Case Fixes +**Learning:** When refactoring regex paths (like `replaceFirst`) into equivalent literal matches (`startsWith`, `endsWith`, `equals`), new specific edge-case branches (like `else if(packagePath.equals(packageSuffix + "."))`) might not have existing unit test coverage. This will trigger Codecov PR failures. +**Action:** When adding new conditional branches to mimic regex edge cases, immediately add corresponding test coverage to prevent Codecov suite failures. diff --git a/org.moreunit.test/test/org/moreunit/matching/ClassNameEvaluationTest.java b/org.moreunit.test/test/org/moreunit/matching/ClassNameEvaluationTest.java new file mode 100644 index 00000000..c879a7c7 --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/matching/ClassNameEvaluationTest.java @@ -0,0 +1,67 @@ +package org.moreunit.matching; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.moreunit.core.matching.FileNameEvaluation; +import org.moreunit.util.JavaType; + +public class ClassNameEvaluationTest { + + @Test + public void should_strip_package_prefix() { + FileNameEvaluation mockEvaluation = mock(FileNameEvaluation.class); + when(mockEvaluation.isTestFile()).thenReturn(true); + when(mockEvaluation.getPreferredCorrespondingFileName()).thenReturn("TheClass"); + + ClassNameEvaluation eval = new ClassNameEvaluation(mockEvaluation, "com.test", null, "com.test.example"); + JavaType javaType = eval.getPreferredCorrespondingClass(); + assertThat(javaType.getQualifier()).isEqualTo("example"); + } + + @Test + public void should_not_strip_package_prefix_if_no_match() { + FileNameEvaluation mockEvaluation = mock(FileNameEvaluation.class); + when(mockEvaluation.isTestFile()).thenReturn(true); + when(mockEvaluation.getPreferredCorrespondingFileName()).thenReturn("TheClass"); + + ClassNameEvaluation eval = new ClassNameEvaluation(mockEvaluation, "com.test", null, "org.example"); + JavaType javaType = eval.getPreferredCorrespondingClass(); + assertThat(javaType.getQualifier()).isEqualTo("org.example"); + } + + @Test + public void should_strip_package_suffix() { + FileNameEvaluation mockEvaluation = mock(FileNameEvaluation.class); + when(mockEvaluation.isTestFile()).thenReturn(true); + when(mockEvaluation.getPreferredCorrespondingFileName()).thenReturn("TheClass"); + + ClassNameEvaluation eval = new ClassNameEvaluation(mockEvaluation, null, "test", "org.example.test"); + JavaType javaType = eval.getPreferredCorrespondingClass(); + assertThat(javaType.getQualifier()).isEqualTo("org.example"); + } + + @Test + public void should_not_strip_package_suffix_if_no_match() { + FileNameEvaluation mockEvaluation = mock(FileNameEvaluation.class); + when(mockEvaluation.isTestFile()).thenReturn(true); + when(mockEvaluation.getPreferredCorrespondingFileName()).thenReturn("TheClass"); + + ClassNameEvaluation eval = new ClassNameEvaluation(mockEvaluation, null, "test", "org.example.dev"); + JavaType javaType = eval.getPreferredCorrespondingClass(); + assertThat(javaType.getQualifier()).isEqualTo("org.example.dev"); + } + + @Test + public void should_add_package_prefix_and_suffix_for_non_test_file() { + FileNameEvaluation mockEvaluation = mock(FileNameEvaluation.class); + when(mockEvaluation.isTestFile()).thenReturn(false); + when(mockEvaluation.getPreferredCorrespondingFileName()).thenReturn("TheClassTest"); + + ClassNameEvaluation eval = new ClassNameEvaluation(mockEvaluation, "com.test", "integration", "org.example"); + JavaType javaType = eval.getPreferredCorrespondingClass(); + assertThat(javaType.getQualifier()).isEqualTo("com.test.org.example.integration"); + } +} diff --git a/org.moreunit.test/test/org/moreunit/util/BaseToolsTest.java b/org.moreunit.test/test/org/moreunit/util/BaseToolsTest.java index 8317fc57..2708ffbb 100644 --- a/org.moreunit.test/test/org/moreunit/util/BaseToolsTest.java +++ b/org.moreunit.test/test/org/moreunit/util/BaseToolsTest.java @@ -98,6 +98,9 @@ public void getTestedClass_should_handle_package_suffix() throws Exception className = "packtest.EinsTest"; assertThat(BaseTools.getTestedClass(className, new String[0], suffixes, null, packageSuffix)).containsExactly("packtest.Eins"); + + className = "test.test.EinsTest"; + assertThat(BaseTools.getTestedClass(className, new String[0], suffixes, null, packageSuffix)).containsExactly("test.Eins"); } @Test From 8ae032b02b7a4a6536c2666b16357fd66d9b80ec Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 02:31:55 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20Replace=20Regex=20prefix/suffix=20matching=20with=20Stri?= =?UTF-8?q?ng=20startsWith/endsWith=20&=20Increase=20SWTBot=20Timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced replaceFirst with regex patterns with startsWith(), endsWith(), and substring() across ClassNameEvaluation.java and BaseTools.java. This optimization eliminates regex compilation and evaluation overhead during test class resolution. Trailing/leading dot requirements were preserved exactly as defined by the original regex. Increased SWTBot test timeout to 20000ms to resolve CI flakiness in GitHub actions. Added missing test coverage for base package packageSuffix edge-case evaluation to satisfy codecov checks. Added bot.sleep to RenameMethodTest to prevent race condition between UI shell closure and Java Model background refactoring updates. Co-authored-by: RoiSoleil <3462260+RoiSoleil@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../src/org/moreunit/refactoring/RenameMethodTest.java | 1 + 2 files changed, 4 insertions(+) diff --git a/.jules/bolt.md b/.jules/bolt.md index 9a655c15..9b3d00c7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -24,3 +24,6 @@ ## 2026-05-21 - Code Coverage Reductions from Edge Case Fixes **Learning:** When refactoring regex paths (like `replaceFirst`) into equivalent literal matches (`startsWith`, `endsWith`, `equals`), new specific edge-case branches (like `else if(packagePath.equals(packageSuffix + "."))`) might not have existing unit test coverage. This will trigger Codecov PR failures. **Action:** When adding new conditional branches to mimic regex edge cases, immediately add corresponding test coverage to prevent Codecov suite failures. +## 2026-05-21 - SWTBot Race Condition in Refactoring Tests +**Learning:** `RenameMethodTest` occasionally fails in CI because the rename refactoring runs asynchronously after the Rename dialog closes. If the JVM hits the assertion before the refactoring `Job` finishes writing to the `ICompilationUnit` model, it sees the old method name and fails. +**Action:** When an Eclipse refactoring is tested in SWTBot, ensure you wait for the specific result (like the new method name appearing in the model) rather than just waiting for the dialog shell to close. Or, artificially sleep the test thread for a second. diff --git a/org.moreunit.swtbot.test/src/org/moreunit/refactoring/RenameMethodTest.java b/org.moreunit.swtbot.test/src/org/moreunit/refactoring/RenameMethodTest.java index d595b5bc..fa212dc8 100644 --- a/org.moreunit.swtbot.test/src/org/moreunit/refactoring/RenameMethodTest.java +++ b/org.moreunit.swtbot.test/src/org/moreunit/refactoring/RenameMethodTest.java @@ -65,5 +65,6 @@ protected void renameMethodAndWaitUntilFinished() SWTBotShell renameDialog = bot.activeShell(); bot.button(IDialogConstants.OK_LABEL).click(); bot.waitUntil(Conditions.shellCloses(renameDialog), 20000); + bot.sleep(3000); // Wait for background refactoring job to update Java Model } }