Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Added

- Unit tests for the different problem detection strategies, making sure there are no duplicate IDs and that every ID maps back to its listener
- Unit tests for the problem filter list, covering text beginning, glob and `re:` entries as well as blank and invalid entries

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.overengineer.inlineproblems.utils;

import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.annotations.VisibleForTesting;
import org.overengineer.inlineproblems.settings.SettingsState;

import java.util.ArrayList;
Expand Down Expand Up @@ -44,11 +45,19 @@ private ProblemTextFilter() {
* @return true if the problem should be ignored because it matches one of the filters
*/
public static boolean isFiltered(String problemText) {
String rawFilterList = SettingsState.getInstance().getProblemFilterList();

return isFiltered(problemText, rawFilterList);
}

@VisibleForTesting
static boolean isFiltered(String problemText, String rawFilterList) {
if (problemText == null) {
return true;
}

List<Predicate<String>> filters = getFilters();
List<Predicate<String>> filters = getFilters(rawFilterList);

if (filters.isEmpty()) {
return false;
}
Expand All @@ -64,9 +73,7 @@ public static boolean isFiltered(String problemText) {
return false;
}

private static List<Predicate<String>> getFilters() {
String rawFilterList = SettingsState.getInstance().getProblemFilterList();

private static List<Predicate<String>> getFilters(String rawFilterList) {
if (!Objects.equals(rawFilterList, cachedRawFilterList)) {
cachedFilters = parse(rawFilterList);
cachedRawFilterList = rawFilterList;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.overengineer.inlineproblems.utils;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.*;

class ProblemTextFilterTest {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests share the static cache in ProblemTextFilter (cachedRawFilterList / cachedFilters) and never reset it, so what a test actually exercises depends on what the previous test left behind. For example, if nullFilterListIsIgnored runs first, Objects.equals(null, null) is true on the initial state and parse(null) is never called. A small package-private resetCache() (@VisibleForTesting) called from a @BeforeEach would make the tests independent of the method order.


@Test
void prefixMatchesTextBegin() {
assertTrue(ProblemTextFilter.isFiltered("Field 'x' is never used", "field"));
assertTrue(ProblemTextFilter.isFiltered("Unused import statement", "unu"));
assertFalse(ProblemTextFilter.isFiltered("Field 'x' is never used", "never"));
assertFalse(ProblemTextFilter.isFiltered("Unused import statement", "import"));
}

@ParameterizedTest
@CsvSource({
"Field 'x' is never used,todo;fIELD",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: ' is JUnit's default quote character in @CsvSource. It works here because the quotes are in the middle of the field, but a row whose text starts with an apostrophe (like 'Thread.sleep()' called in loop used further down) would be parsed as a quoted field. Setting quoteCharacter = '"' avoids that trap for future rows.

"Unused import statement,UNU",
"Field 'x' is never used,re:NEVER",
"Unused import statement,*sTaT*;todo"
})
void caseIsIgnored(String problemText, String rawFilterList) {
assertTrue(ProblemTextFilter.isFiltered(problemText, rawFilterList));
}

@Test
void blankFilterTextIsIgnored() {
assertFalse(ProblemTextFilter.isFiltered("Unused import statement", ""));
assertFalse(ProblemTextFilter.isFiltered("Unused import statement", ";;"));
assertFalse(ProblemTextFilter.isFiltered("Unused import statement", "; ;"));
assertFalse(ProblemTextFilter.isFiltered("Cannot resolve method 'foo(int)'", "re:"));
}

@Test
void leadingSpacesAreIgnored() {
assertTrue(ProblemTextFilter.isFiltered("Field 'x' is never used", " field"));
assertTrue(ProblemTextFilter.isFiltered("Unused import statement", " *use*"));
assertTrue(ProblemTextFilter.isFiltered(" Unused import statement", "Unu"));
}

@Test
void globMatchesTextPattern() {
assertTrue(ProblemTextFilter.isFiltered("Field 'x' is never used", "fie*"));
assertTrue(ProblemTextFilter.isFiltered("Unused import statement", "*t st*"));
assertFalse(ProblemTextFilter.isFiltered("Field 'x' is never used", "field?"));
assertTrue(ProblemTextFilter.isFiltered("Unused import statement", "*statement"));
}

@Test
void globIgnoresRegexSymbols() {
assertTrue(ProblemTextFilter.isFiltered("Cannot resolve method 'foo(int)'", "*foo(int)?"));
assertTrue(ProblemTextFilter.isFiltered("Cannot resolve method 'foo(int)'", "*foo(*"));
assertTrue(ProblemTextFilter.isFiltered("'Thread.sleep()' called in loop", "*ad.sleep*"));
assertFalse(ProblemTextFilter.isFiltered("'Thread.sleep' called in loop", "*sleep()*"));
assertFalse(ProblemTextFilter.isFiltered("'ThreadXsleep()' called in loop", "*ad.sleep*"));
}

@Test
void regexMatchesTextPattern() {
assertTrue(ProblemTextFilter.isFiltered("'ThreadXsleep()' called in loop", "re:ad.sleep"));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test for an entry that both starts with re: and contains */?, so the regex-before-glob precedence in toFilter() is not covered. If the two checks were swapped, re:foo.*bar would become a glob with a literal re: prefix and no test would fail. Something like assertTrue(ProblemTextFilter.isFiltered("foo x bar", "re:foo.*bar")) would pin it down.

}

@Test
void regexIdentifierCaseIsIgnored() {
assertTrue(ProblemTextFilter.isFiltered("'ThreadXsleep()' called in loop", "RE:ad.sleep"));
}

@Test
void invalidRegexIsIgnored() {
assertDoesNotThrow(() -> ProblemTextFilter.isFiltered("Cannot resolve method 'foo(int)'", "re:foo("));
assertTrue(ProblemTextFilter.isFiltered("Unused import statement", "re:imp(;unused"));
}

@Test
void nullTextIsIgnored() {
assertTrue(ProblemTextFilter.isFiltered(null, ";;"));
}

@Test
void nullFilterListIsIgnored() {
assertFalse(ProblemTextFilter.isFiltered("Unused import statement", null));
}

@Test
void changedFilterListIsApplied() {
assertTrue(ProblemTextFilter.isFiltered("Field 'x' is never used", "field"));
assertFalse(ProblemTextFilter.isFiltered("Field 'x' is never used", "*fiel"));
assertFalse(ProblemTextFilter.isFiltered("Field 'x' is never used", "*fiel"));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same assertion as the line above, so the sequence checks less than it looks like. Was this meant to use a different list or text? With only two lists that alternate, the test would also pass with a cache that is rebuilt on every call, so a third distinct list (e.g. field → *fiel → re:never → field) would make it actually exercise the invalidation.

assertTrue(ProblemTextFilter.isFiltered("Field 'x' is never used", "field"));
}
}
Loading