Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
Expand All @@ -37,6 +38,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@TestResourceScope.Scoped
class ExpressionsHelperTest extends JParserTestUtils {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import java.util.List;
import java.util.stream.Collectors;

import org.sonar.java.model.JParser;
import org.sonar.java.model.FrontendParser;
import org.sonar.java.model.JParserConfig;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.tree.*;

@TestResourceScope.Scoped
public abstract class JParserTestUtils {

static IdentifierTree variableFromLastReturnStatement(List<StatementTree> statements) {
Expand Down Expand Up @@ -77,7 +79,7 @@ public static CompilationUnitTree parse(File file) {
public static CompilationUnitTree parse(String source) {
List<File> classpath = Arrays.asList(new File("target/test-classes"), new File("target/classes"));
JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION;
return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), "test", source);
return TestResourceScope.retain(FrontendParser.parse(version, classpath, "test", source)).tree();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import java.util.List;

import org.junit.jupiter.api.Test;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.*;

import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static org.assertj.core.api.Assertions.assertThat;

@TestResourceScope.Scoped
class ReassignmentFinderTest extends JParserTestUtils {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.java.model.statement.BlockTreeImpl;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ForEachStatement;
Expand All @@ -36,6 +37,7 @@

import static org.assertj.core.api.Assertions.assertThat;

@TestResourceScope.Scoped
class TreeHelperTest extends JParserTestUtils {

@Test
Expand Down
5 changes: 5 additions & 0 deletions java-checks-test-sources/test-classpath-reader/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<description>Code Analyzer for Java :: Test Classpath Reader</description>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayDeque;
import java.util.Deque;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

/** Retains lazy fixtures through instance initialization, nested tests and all assertions in a test class. */
public final class TestResourceScope implements BeforeAllCallback, AfterAllCallback {

private static final ThreadLocal<Deque<Scope>> SCOPES = ThreadLocal.withInitial(ArrayDeque::new);
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(TestResourceScope.class);

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith(TestResourceScope.class)
@Execution(ExecutionMode.SAME_THREAD)
public @interface Scoped {
}

public static Scope open() {
Scope scope = new Scope();
SCOPES.get().push(scope);
return scope;
}

public static <T extends AutoCloseable> T retain(T resource) {
Scope scope = SCOPES.get().peek();
if (scope == null) {
IllegalStateException failure = new IllegalStateException("Parser fixtures require @TestResourceScope.Scoped or an explicit scope");
try {
resource.close();
} catch (Exception cleanupFailure) {
failure.addSuppressed(cleanupFailure);
}
throw failure;
}
scope.resources.push(resource);
return resource;
}

@Override
public void beforeAll(ExtensionContext context) {
context.getStore(NAMESPACE).put("scope", open());
}

@Override
public void afterAll(ExtensionContext context) throws Exception {
Scope scope = context.getStore(NAMESPACE).remove("scope", Scope.class);
if (scope != null) {
scope.close();
}
}

public static final class Scope implements AutoCloseable {
private final Deque<AutoCloseable> resources = new ArrayDeque<>();
private boolean closed;

private Scope() {
}

/** Attempts every fixture even after errors, then rethrows the first failure with later failures suppressed. */
@Override
public void close() throws Exception {
if (closed) {
return;
}
if (SCOPES.get().peek() != this) {
throw new IllegalStateException("Test resource scopes must close in reverse order on their owning thread");
}
closed = true;
SCOPES.get().pop();
if (SCOPES.get().isEmpty()) {
SCOPES.remove();
}
Throwable failure = null;
try {
while (!resources.isEmpty()) {
try {
resources.pop().close();
} catch (Exception | Error e) {
if (failure == null) {
failure = e;
} else if (failure != e) {
failure.addSuppressed(e);
}
}
}
} finally {
resources.clear();
}
if (failure instanceof Error error) {
throw error;
}
if (failure instanceof Exception exception) {
throw exception;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.test;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class TestResourceScopeTest {

@Test
void nested_scopes_close_only_their_own_resources_after_assertions() throws Exception {
List<String> calls = new ArrayList<>();
try (TestResourceScope.Scope outer = TestResourceScope.open()) {
TestResourceScope.retain(() -> calls.add("outer"));
try (TestResourceScope.Scope inner = TestResourceScope.open()) {
TestResourceScope.retain(() -> calls.add("inner"));
assertThat(calls).isEmpty();
}
assertThat(calls).containsExactly("inner");
}
assertThat(calls).containsExactly("inner", "outer");
}

@Test
void scope_closes_every_resource_in_reverse_order_even_when_cleanup_fails() throws Exception {
TestResourceScope.Scope scope = TestResourceScope.open();
List<String> calls = new ArrayList<>();
IllegalStateException first = new IllegalStateException("first");
Exception second = new Exception("second");
TestResourceScope.retain(() -> calls.add("last"));
TestResourceScope.retain(() -> { calls.add("second"); throw second; });
TestResourceScope.retain(() -> { calls.add("first"); throw first; });
assertThatThrownBy(scope::close).isSameAs(first).hasSuppressedException(second);
scope.close();
assertThat(calls).containsExactly("first", "second", "last");
}

@Test
void scope_cleanup_attempts_every_resource_and_suppresses_errors() throws Exception {
TestResourceScope.Scope scope = TestResourceScope.open();
List<String> calls = new ArrayList<>();
AssertionError failure = new AssertionError("cleanup");
TestResourceScope.retain(() -> calls.add("last"));
TestResourceScope.retain(() -> { throw failure; });
Exception first = new Exception("first");
TestResourceScope.retain(() -> { throw first; });

assertThatThrownBy(scope::close).isSameAs(first).hasSuppressedException(failure);
scope.close();
assertThat(calls).containsExactly("last");
try (TestResourceScope.Scope next = TestResourceScope.open()) {
TestResourceScope.retain(() -> calls.add("next"));
}
assertThat(calls).containsExactly("last", "next");
}

@Test
void scope_cleanup_rethrows_first_error_after_attempting_every_resource() throws Exception {
TestResourceScope.Scope scope = TestResourceScope.open();
List<String> calls = new ArrayList<>();
AssertionError first = new AssertionError("first");
Exception second = new Exception("second");
TestResourceScope.retain(() -> calls.add("last"));
TestResourceScope.retain(() -> { throw second; });
TestResourceScope.retain(() -> { throw first; });
TestResourceScope.retain(() -> { throw first; });

assertThatThrownBy(scope::close).isSameAs(first).hasSuppressedException(second);
scope.close();
assertThat(calls).containsExactly("last");
}

@Test
void missing_scope_fails_loudly_and_closes_the_unretained_fixture() {
List<String> calls = new ArrayList<>();
assertThatThrownBy(() -> TestResourceScope.retain(() -> calls.add("closed")))
.isInstanceOf(IllegalStateException.class).hasMessageContaining("@TestResourceScope.Scoped");
assertThat(calls).containsExactly("closed");
}

@Test
void missing_scope_preserves_cleanup_exceptions_as_suppressed() {
Exception failure = new Exception("cleanup failed");
assertThatThrownBy(() -> TestResourceScope.retain(() -> { throw failure; }))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("@TestResourceScope.Scoped")
.hasSuppressedException(failure);
}

@Test
void missing_scope_propagates_cleanup_errors() {
AssertionError failure = new AssertionError("cleanup failed");
assertThatThrownBy(() -> TestResourceScope.retain(() -> { throw failure; }))
.isSameAs(failure);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.java.checks.helpers.JParserTestUtils;
import org.sonar.java.checks.verifier.CheckVerifier;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.ImportTree;
import org.sonar.plugins.java.api.tree.Tree;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

@TestResourceScope.Scoped
class ImportDeclarationOrderCheckTest {
@Test
@DisplayName("No issues with properly ordered imports including modules")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

import org.junit.jupiter.api.Test;
import org.sonar.java.model.JavaVersionImpl;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@TestResourceScope.Scoped
class ClassPatternsUtilsTest {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

@TestResourceScope.Scoped
class DeprecatedCheckerHelperTest {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.sonar.java.test.TestResourceScope;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.LiteralTree;

import static org.assertj.core.api.Assertions.assertThat;

@TestResourceScope.Scoped
class GeneratedStringLiteralRecognizerTest {

@Test
Expand Down
Loading
Loading