Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@
## 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).
## 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.
## 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.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 15 additions & 3 deletions org.moreunit.plugin/src/org/moreunit/util/BaseTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,23 @@ public static List<String> 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 = "";
}
}
}

Expand All @@ -69,7 +81,7 @@ public static List<String> getTestedClass(String testCaseClass, String[] prefixe
{
if(typeName.startsWith(prefix))
{
results.add(packagePath + typeName.replaceFirst(prefix, ""));
results.add(packagePath + typeName.substring(prefix.length()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
3 changes: 3 additions & 0 deletions org.moreunit.test/test/org/moreunit/util/BaseToolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading