Skip to content

Commit

Permalink
Escape regex-relevant characters in test filter
Browse files Browse the repository at this point in the history
Go tests can contain arbitrary characters because they're not tied to a
symbol name in a source file. Accordingly, they may contain characters
which have special meaning for regexes. Because the Go test runner
interprets this flag as a regex, we need to escape these.

Without this, running a single test from the gutter, Bazel will exit 0
and look happy, but it won't have actually run the test (which perhaps
would have failed if it had run).
  • Loading branch information
illicitonion committed Nov 21, 2023
1 parent 756a76a commit 0dfff01
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.goide.execution.testing.GoTestRunConfigurationProducerBase;
import com.goide.psi.GoFile;
import com.goide.psi.GoFunctionOrMethodDeclaration;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.dependencies.TargetInfo;
import com.google.idea.blaze.base.run.ExecutorType;
Expand Down Expand Up @@ -47,18 +48,43 @@ class GoTestContextProvider implements TestContextProvider {
* When a user clicks in the ">" button, we get pointed to the "Run" part of "t.Run".
* We need to walk the tree up to see the function call that has the actual argument.
*
* This function doesn't worry about turning this filter into a regex which can be passed to --test_filter.
* @see #regexifyTestFilter for converting this to a --test_filter value.
*
* @param element: Element the user has clicked on.
* @param enclosingFunction: Go function encompassing this test.
* @return String representation of the proper test filter.
*/
private String calculateTestFilter(PsiElement element, GoFunctionOrMethodDeclaration enclosingFunction) {
private static String calculateRawTestFilterForElement(PsiElement element, GoFunctionOrMethodDeclaration enclosingFunction) {
if (element.getParent().isEquivalentTo(enclosingFunction)) {
return enclosingFunction.getName();
} else {
return GoTestRunConfigurationProducerBase.findSubTestInContext(element, enclosingFunction);
}
}

@VisibleForTesting
static String regexifyTestFilter(String testFilter) {
return "^" + escapeRegexChars(testFilter) + "$";
}

private static String escapeRegexChars(String name) {
StringBuilder output = new StringBuilder();
for (char c : name.toCharArray()) {
if (isRegexCharNeedingEscaping(c)) {
output.append("\\");
}
output.append(c);
}
return output.toString();
}

private static boolean isRegexCharNeedingEscaping(char c) {
// Taken from https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/regexp/regexp.go;l=720
return c == '\\' || c == '.' || c == '+' || c == '*' || c == '?' || c == '(' || c == ')' || c == '|' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '$';
}


public static final String GO_TEST_WRAP_TESTV = "GO_TEST_WRAP_TESTV=1";
@Nullable
@Override
Expand All @@ -84,11 +110,11 @@ public RunConfigurationContext getTestContext(ConfigurationContext context) {
.setDescription(file.getName())
.build();
}
String testFilter = calculateTestFilter(element, function);
String testFilterRegex = regexifyTestFilter(calculateRawTestFilterForElement(element, function));
return TestContext.builder(/* sourceElement= */ function, ExecutorType.DEBUG_SUPPORTED_TYPES)
.addTestEnv(GO_TEST_WRAP_TESTV)
.setTarget(target)
.setTestFilter("^" + testFilter + "$")
.setTestFilter(testFilterRegex)
.setDescription(String.format("%s#%s", file.getName(), function.getName()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,14 @@ public void testNestedTests() throws Throwable {
"func TestFoo(t *testing.T) {",
" t.R<caret>un(\"with_nested\", func(t *testing.T) {",
" t.R<caret>un(\"subtest\", func(t *testing.T) {})",
" t.R<caret>un(\"subtest (great)\", func(t *testing.T) {})",
" })",
"}"
);

assertElementGeneratesTestFilter(getElementAtCaret(0, goFile), "^TestFoo/with_nested$");
assertElementGeneratesTestFilter(getElementAtCaret(1, goFile), "^TestFoo/with_nested/subtest$");
assertElementGeneratesTestFilter(getElementAtCaret(2, goFile), "^TestFoo/with_nested/subtest_\\(great\\)$");
}
@Test
public void testCodeBetweenTests() throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.google.idea.blaze.golang.run.producers;

import com.google.idea.blaze.base.BlazeTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static com.google.common.truth.Truth.assertThat;

@RunWith(JUnit4.class)
public class GoTestContextProviderTest extends BlazeTestCase {
@Test
public void testRegexifyTestFilter() {
String regexified = GoTestContextProvider.regexifyTestFilter("Test/with/subtest(good \\ or \\ bad)");
assertThat(regexified).isEqualTo("^Test/with/subtest\\(good \\\\ or \\\\ bad\\)$");
}
}

0 comments on commit 0dfff01

Please sign in to comment.