From 48df12256777fbbab9c174e035065d35c01a0b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Mon, 2 Nov 2026 12:01:04 +0100 Subject: [PATCH 1/3] Introduce frontend sessions and deterministic resource ownership --- .../java/org/sonar/java/JavaFrontend.java | 25 +- .../java/org/sonar/java/ProgressMonitor.java | 11 +- .../org/sonar/java/ast/JavaAstScanner.java | 15 +- .../sonar/java/model/EcjFrontendBackend.java | 96 +++++ .../sonar/java/model/EcjProgressMonitor.java | 69 ++++ .../org/sonar/java/model/FrontendBackend.java | 23 ++ .../org/sonar/java/model/FrontendBatch.java | 49 +++ .../java/model/FrontendConfiguration.java | 32 ++ .../org/sonar/java/model/FrontendParser.java | 68 ++++ .../sonar/java/model/FrontendResources.java | 96 +++++ .../org/sonar/java/model/FrontendSession.java | 45 +++ .../java/org/sonar/java/model/JParser.java | 14 +- .../org/sonar/java/model/JParserConfig.java | 117 ++---- .../main/java/org/sonar/java/model/JSema.java | 12 +- .../java/model/FrontendLifecycleTest.java | 333 ++++++++++++++++++ 15 files changed, 893 insertions(+), 112 deletions(-) create mode 100644 java-frontend/src/main/java/org/sonar/java/model/EcjFrontendBackend.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/EcjProgressMonitor.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendBackend.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendBatch.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendConfiguration.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendParser.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendResources.java create mode 100644 java-frontend/src/main/java/org/sonar/java/model/FrontendSession.java create mode 100644 java-frontend/src/test/java/org/sonar/java/model/FrontendLifecycleTest.java diff --git a/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java b/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java index 09c44fc0ac2..8c44cd635fd 100644 --- a/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java +++ b/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,6 +39,7 @@ import org.sonar.java.collections.CollectionUtils; import org.sonar.java.exceptions.ApiMismatchException; import org.sonar.java.filters.SonarJavaIssueFilter; +import org.sonar.java.model.FrontendResources; import org.sonar.java.model.JParserConfig; import org.sonar.java.model.VisitorsBridge; import org.sonar.java.telemetry.Telemetry; @@ -236,23 +236,22 @@ private void scanInBatches(BatchModeContext context, List allInputFil private void scanBatch(BatchModeContext context, List batchFiles, AnalysisProgress analysisProgress) { analysisProgress.startBatch(batchFiles.size()); - Set environmentsCleaners = new HashSet<>(); - JParserConfig.Mode.BATCH - .create(javaVersion, context.getClasspath(), sonarComponents.shouldIgnoreUnnamedModuleForSplitPackage()) - .parse(batchFiles, sonarComponents::analysisCancelled, analysisProgress, (input, result) -> scanAsBatchCallback(input, result, context, environmentsCleaners)); - // Due to a bug in ECJ, JAR files remain locked after the analysis on Windows, we unlock them manually, at the end of each batches. See SONARJAVA-3609. - environmentsCleaners.forEach(Runnable::run); + try (FrontendResources environmentsCleaners = new FrontendResources()) { + JParserConfig.Mode.BATCH + .create(javaVersion, context.getClasspath(), sonarComponents.shouldIgnoreUnnamedModuleForSplitPackage()) + .parse(batchFiles, sonarComponents::analysisCancelled, analysisProgress, (input, result) -> scanAsBatchCallback(input, result, context, environmentsCleaners)); + } analysisProgress.endBatch(); } - private static void scanAsBatchCallback(InputFile inputFile, JParserConfig.Result result, BatchModeContext context, Set environmentsCleaners) { + private static void scanAsBatchCallback(InputFile inputFile, JParserConfig.Result result, BatchModeContext context, FrontendResources environmentsCleaners) { JavaAstScanner scanner = context.selectScanner(inputFile); Duration duration = PerformanceMeasure.start(context.descriptor(inputFile)); - scanner.simpleScan(inputFile, result, ast -> - // In batch mode, we delay the cleaning of the environment as it will be used in later processing. - environmentsCleaners.add(ast.sema.getEnvironmentCleaner()) - ); - duration.stop(); + try { + scanner.simpleScan(inputFile, result, ast -> environmentsCleaners.own(ast.sema.getEnvironmentCleaner())); + } finally { + duration.stop(); + } } interface BatchModeContext { diff --git a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java index 74e3e81452c..dd1c71e52a2 100644 --- a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java +++ b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java @@ -19,12 +19,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; -import org.eclipse.core.runtime.IProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.java.annotations.VisibleForTesting; -public class ProgressMonitor implements IProgressMonitor, Runnable { +public class ProgressMonitor implements Runnable { private final Logger logger; private final long period; @@ -84,7 +83,6 @@ public void run() { } } - @Override public void beginTask(String name, int totalWork) { if (totalWork <= 0) { unknownTotalWork = true; @@ -96,7 +94,6 @@ public void beginTask(String name, int totalWork) { thread.start(); } - @Override public void done() { if (success && analysisProgress.isLastBatch()) { log("100% analyzed"); @@ -107,7 +104,6 @@ public void done() { join(); } - @Override public boolean isCanceled() { if (isCanceled.getAsBoolean()) { log("Batch processing: Cancelled!"); @@ -116,7 +112,6 @@ public boolean isCanceled() { return false; } - @Override public void setCanceled(boolean value) { // do nothing } @@ -129,7 +124,6 @@ private void join() { } } - @Override public void worked(int work) { processedWork += work; if (processedWork == totalWork) { @@ -137,17 +131,14 @@ public void worked(int work) { } } - @Override public void internalWorked(double work) { // do nothing } - @Override public void setTaskName(String name) { // do nothing } - @Override public void subTask(String name) { // do nothing } diff --git a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java index cbfb0b41964..f7bf25fb650 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java @@ -37,6 +37,7 @@ import org.sonar.java.AnalysisProgress; import org.sonar.java.SonarComponents; import org.sonar.java.annotations.VisibleForTesting; +import org.sonar.java.model.FrontendResources; import org.sonar.java.model.InputFileUtils; import org.sonar.java.model.JParserConfig; import org.sonar.java.model.JProblem; @@ -163,12 +164,14 @@ public void simpleScan(InputFile inputFile, JParserConfig.Result result, Consume var telemetryAnalysisKeys = javaAnalysisKeys.exceptions(); try { JavaTree.CompilationUnitTreeImpl ast = result.get(); - modifyCompilationUnit.accept(ast); - visitor.visitFile(ast, sonarComponents != null && sonarComponents.fileCanBeSkipped(inputFile)); - String path = inputFile.toString(); - Set undefinedTypes = ast.undefinedTypes(); - collectUndefinedTypes(path, undefinedTypes); - cleanUp.accept(ast); + Set undefinedTypes; + try (FrontendResources resources = new FrontendResources()) { + resources.own(() -> cleanUp.accept(ast)); + modifyCompilationUnit.accept(ast); + visitor.visitFile(ast, sonarComponents != null && sonarComponents.fileCanBeSkipped(inputFile)); + undefinedTypes = ast.undefinedTypes(); + collectUndefinedTypes(inputFile.toString(), undefinedTypes); + } telemetryAnalysisKeys = javaAnalysisKeys.success(); telemetry.aggregateAsCounter(javaAnalysisKeys.success().typeErrorCountKey(), undefinedTypes.size()); } catch (RecognitionException e) { diff --git a/java-frontend/src/main/java/org/sonar/java/model/EcjFrontendBackend.java b/java-frontend/src/main/java/org/sonar/java/model/EcjFrontendBackend.java new file mode 100644 index 00000000000..9b36a94bd77 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/EcjFrontendBackend.java @@ -0,0 +1,96 @@ +/* + * 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.model; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTUtils; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.java.ProgressMonitor; + +final class EcjFrontendBackend implements FrontendBackend { + + private final Supplier parserFactory; + + EcjFrontendBackend(Supplier parserFactory) { + this.parserFactory = parserFactory; + } + + @Override + public FrontendSession open(FrontendConfiguration configuration) { + return new Session(configuration, parserFactory.get()); + } + + static ASTParser newParser(FrontendConfiguration configuration) { + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + Map options = new HashMap<>(JavaCore.getOptions()); + JavaCore.setComplianceOptions(configuration.sourceVersion(), options); + options.put(JavaCore.COMPILER_PB_MAX_PER_UNIT, "42000"); + if (configuration.ignoreUnnamedModuleForSplitPackage()) { + options.put(JavaCore.COMPILER_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE, "enabled"); + } + if (configuration.previewEnabled()) { + options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); + } + EcjDiagnosticProvider.compilerOptions().forEach(option -> options.put(option, "warning")); + parser.setCompilerOptions(options); + boolean includeRunningVMBootclasspath = configuration.classpath().stream() + .noneMatch(file -> Set.of("rt.jar", "jrt-fs.jar", "android.jar").contains(file.getName())); + parser.setEnvironment(configuration.classpath().stream().map(File::getAbsolutePath).toArray(String[]::new), + new String[] {}, new String[] {}, includeRunningVMBootclasspath); + parser.setResolveBindings(true); + parser.setBindingsRecovery(true); + return parser; + } + + private static final class Session extends FrontendSession { + + private final FrontendConfiguration configuration; + private final ASTParser parser; + + private Session(FrontendConfiguration configuration, ASTParser parser) { + this.configuration = configuration; + this.parser = parser; + } + + @Override + JavaTree.CompilationUnitTreeImpl parse(String unitName, String source) { + return JParser.parse(parser, configuration.sourceVersion(), unitName, source, ast -> own(ASTUtils.getEnvironmentCleaner(ast.getAST()))); + } + + @Override + void parseBatch(FrontendBatch inputs, ProgressMonitor monitor, BiConsumer action) { + parser.createASTs(inputs.paths().toArray(String[]::new), inputs.encodings().toArray(String[]::new), new String[0], new FileASTRequestor() { + @Override + public void acceptAST(String sourceFilePath, CompilationUnit ast) { + Runnable cleaner = own(ASTUtils.getEnvironmentCleaner(ast.getAST())); + InputFile input = inputs.byPath().get(new File(sourceFilePath)); + action.accept(input, () -> JParser.convert(configuration.sourceVersion(), input.filename(), input.contents(), ast, cleaner)); + } + }, new EcjProgressMonitor(monitor)); + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/EcjProgressMonitor.java b/java-frontend/src/main/java/org/sonar/java/model/EcjProgressMonitor.java new file mode 100644 index 00000000000..2622e6bcb0b --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/EcjProgressMonitor.java @@ -0,0 +1,69 @@ +/* + * 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.model; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.sonar.java.ProgressMonitor; + +final class EcjProgressMonitor implements IProgressMonitor { + + private final ProgressMonitor delegate; + + EcjProgressMonitor(ProgressMonitor delegate) { + this.delegate = delegate; + } + + @Override + public void beginTask(String name, int totalWork) { + delegate.beginTask(name, totalWork); + } + + @Override + public void done() { + delegate.done(); + } + + @Override + public void worked(int work) { + delegate.worked(work); + } + + @Override + public boolean isCanceled() { + return delegate.isCanceled(); + } + + @Override + public void setCanceled(boolean value) { + delegate.setCanceled(value); + } + + @Override + public void internalWorked(double work) { + delegate.internalWorked(work); + } + + @Override + public void setTaskName(String name) { + delegate.setTaskName(name); + } + + @Override + public void subTask(String name) { + delegate.subTask(name); + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendBackend.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendBackend.java new file mode 100644 index 00000000000..030a6ed9ce7 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendBackend.java @@ -0,0 +1,23 @@ +/* + * 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.model; + +interface FrontendBackend { + + FrontendSession open(FrontendConfiguration configuration); + +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendBatch.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendBatch.java new file mode 100644 index 00000000000..09f7e7ccb1e --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendBatch.java @@ -0,0 +1,49 @@ +/* + * 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.model; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sonar.api.batch.fs.InputFile; + +record FrontendBatch(List inputs, List paths, List encodings, Map byPath) { + + FrontendBatch { + inputs = List.copyOf(inputs); + paths = List.copyOf(paths); + encodings = List.copyOf(encodings); + byPath = Map.copyOf(byPath); + } + + static FrontendBatch from(Iterable inputFiles) { + List inputs = new ArrayList<>(); + List paths = new ArrayList<>(); + List encodings = new ArrayList<>(); + Map byPath = new HashMap<>(); + for (InputFile input : inputFiles) { + String path = input.absolutePath(); + byPath.put(new File(path), input); + paths.add(path); + encodings.add(input.charset().name()); + inputs.add(input); + } + return new FrontendBatch(inputs, paths, encodings, byPath); + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendConfiguration.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendConfiguration.java new file mode 100644 index 00000000000..7c9fe9497d8 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendConfiguration.java @@ -0,0 +1,32 @@ +/* + * 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.model; + +import java.io.File; +import java.util.List; +import org.sonar.plugins.java.api.JavaVersion; + +record FrontendConfiguration(String sourceVersion, boolean previewEnabled, List classpath, boolean ignoreUnnamedModuleForSplitPackage) { + + FrontendConfiguration { + classpath = List.copyOf(classpath); + } + + static FrontendConfiguration from(JavaVersion version, List classpath, boolean ignoreUnnamedModuleForSplitPackage) { + return new FrontendConfiguration(version.effectiveJavaVersionAsString(), version.arePreviewFeaturesEnabled(), classpath, ignoreUnnamedModuleForSplitPackage); + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendParser.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendParser.java new file mode 100644 index 00000000000..4d03b40e8cd --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendParser.java @@ -0,0 +1,68 @@ +/* + * 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.model; + +import java.io.File; +import java.util.List; +import org.sonar.plugins.java.api.JavaVersion; + +public final class FrontendParser { + + private FrontendParser() { + } + + public static ParsedFile parse(JavaVersion version, List classpath, String unitName, String source) { + JParserConfig config = JParserConfig.Mode.FILE_BY_FILE.create(version, classpath); + return parse(config.backend(), config.configuration, unitName, source); + } + + /** Closes the session after parsing exceptions or errors, preserving the parsing failure and suppressing cleanup failures. */ + static ParsedFile parse(FrontendBackend backend, FrontendConfiguration configuration, String unitName, String source) { + FrontendSession session = backend.open(configuration); + try { + return new ParsedFile(session.parse(unitName, source), session); + } catch (RuntimeException | Error failure) { + try { + session.close(); + } catch (RuntimeException | Error cleanupFailure) { + if (failure != cleanupFailure) { + failure.addSuppressed(cleanupFailure); + } + } + throw failure; + } + } + + public static final class ParsedFile implements AutoCloseable { + private final JavaTree.CompilationUnitTreeImpl tree; + private final FrontendSession session; + + private ParsedFile(JavaTree.CompilationUnitTreeImpl tree, FrontendSession session) { + this.tree = tree; + this.session = session; + } + + public JavaTree.CompilationUnitTreeImpl tree() { + return tree; + } + + @Override + public void close() { + session.close(); + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendResources.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendResources.java new file mode 100644 index 00000000000..129dccd1145 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendResources.java @@ -0,0 +1,96 @@ +/* + * 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.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** Owns cleanup handles until the last supported semantic consumer finishes. */ +public final class FrontendResources implements AutoCloseable { + + private final Map cleaners = new LinkedHashMap<>(); + private boolean closed; + + public Runnable own(Runnable cleaner) { + if (closed) { + throw new IllegalStateException("Frontend resources are closed"); + } + return cleaners.computeIfAbsent(cleaner, FrontendResources::idempotent); + } + + static Runnable idempotent(Runnable cleaner) { + return new Cleaner(cleaner); + } + + /** Attempts every cleaner even after errors, then rethrows the first failure with later failures suppressed. */ + @Override + public void close() { + if (closed) { + return; + } + closed = true; + Throwable failure = null; + try { + for (Runnable cleaner : cleaners.values()) { + try { + cleaner.run(); + } catch (RuntimeException | Error e) { + if (failure == null) { + failure = e; + } else if (failure != e) { + failure.addSuppressed(e); + } + } + } + } finally { + cleaners.clear(); + } + if (failure instanceof Error error) { + throw error; + } + if (failure instanceof RuntimeException exception) { + throw exception; + } + } + + private static final class Cleaner implements Runnable { + private final Runnable delegate; + private boolean cleaned; + + private Cleaner(Runnable delegate) { + this.delegate = delegate; + } + + @Override + public synchronized void run() { + if (!cleaned) { + cleaned = true; + delegate.run(); + } + } + + @Override + public boolean equals(Object other) { + return this == other || (other instanceof Cleaner cleaner && delegate.equals(cleaner.delegate)); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/FrontendSession.java b/java-frontend/src/main/java/org/sonar/java/model/FrontendSession.java new file mode 100644 index 00000000000..b75067bf671 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/FrontendSession.java @@ -0,0 +1,45 @@ +/* + * 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.model; + +import java.io.IOException; +import java.util.function.BiConsumer; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.java.ProgressMonitor; + +abstract class FrontendSession implements AutoCloseable { + + private final FrontendResources resources = new FrontendResources(); + + final Runnable own(Runnable cleaner) { + return resources.own(cleaner); + } + + abstract JavaTree.CompilationUnitTreeImpl parse(String unitName, String source); + + abstract void parseBatch(FrontendBatch inputs, ProgressMonitor monitor, BiConsumer action); + + @Override + public void close() { + resources.close(); + } + + @FunctionalInterface + interface ParsedUnit { + JavaTree.CompilationUnitTreeImpl convert() throws IOException; + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParser.java b/java-frontend/src/main/java/org/sonar/java/model/JParser.java index 6ddf24dee0d..4adbc9e5539 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParser.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParser.java @@ -28,6 +28,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -302,6 +303,10 @@ private KeywordSuper keywordSuper(InternalSyntaxToken token, @Nullable ITypeBind * @throws RecognitionException in case of syntax errors */ public static JavaTree.CompilationUnitTreeImpl parse(ASTParser astParser, String version, String unitName, String source) { + return parse(astParser, version, unitName, source, ast -> null); + } + + static JavaTree.CompilationUnitTreeImpl parse(ASTParser astParser, String version, String unitName, String source, Function environmentOwner) { astParser.setUnitName(unitName); astParser.setSource(source.toCharArray()); @@ -313,10 +318,15 @@ public static JavaTree.CompilationUnitTreeImpl parse(ASTParser astParser, String throw new RecognitionException(-1, "ECJ: Unable to parse file.", e); } - return convert(version, unitName, source, astNode); + return convert(version, unitName, source, astNode, environmentOwner.apply(astNode)); } static JavaTree.CompilationUnitTreeImpl convert(String version, String unitName, String source, CompilationUnit astNode) { + return convert(version, unitName, source, astNode, null); + } + + static JavaTree.CompilationUnitTreeImpl convert(String version, String unitName, String source, CompilationUnit astNode, + @Nullable Runnable environmentCleaner) { List errors = Stream.of(astNode.getProblems()).filter(IProblem::isError).toList(); Optional possibleSyntaxError = errors.stream().filter(IS_SYNTAX_ERROR).findFirst(); LineColumnConverter lineColumnConverter = new LineColumnConverter(source); @@ -336,7 +346,7 @@ static JavaTree.CompilationUnitTreeImpl convert(String version, String unitName, .collect(Collectors.toSet()); JParser converter = new JParser(); - converter.sema = new JSema(astNode.getAST()); + converter.sema = new JSema(astNode.getAST(), environmentCleaner); converter.compilationUnit = astNode; converter.tokenManager = createTokenManager(version, unitName, source); converter.lineColumnConverter = lineColumnConverter; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java index 5ef2bb5400c..8debd5fa9a2 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java @@ -17,24 +17,15 @@ package org.sonar.java.model; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.stream.StreamSupport; import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; @@ -53,17 +44,10 @@ public abstract class JParserConfig { private static final Logger LOG = LoggerFactory.getLogger(JParserConfig.class); - private static final String MAXIMUM_ECJ_WARNINGS = "42000"; - private static final Set JRE_JARS = new HashSet<>(Arrays.asList("rt.jar", "jrt-fs.jar", "android.jar")); - - final JavaVersion javaVersion; - final List classpath; - final boolean shouldIgnoreUnnamedModuleForSplitPackage; + final FrontendConfiguration configuration; private JParserConfig(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { - this.javaVersion = javaVersion; - this.classpath = classpath; - this.shouldIgnoreUnnamedModuleForSplitPackage = shouldIgnoreUnnamedModuleForSplitPackage; + this.configuration = FrontendConfiguration.from(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); } public abstract void parse(Iterable inputFiles, BooleanSupplier isCanceled, @@ -114,33 +98,15 @@ public JavaTree.CompilationUnitTreeImpl get() throws Exception { } public ASTParser astParser() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - Map options = new HashMap<>(JavaCore.getOptions()); - JavaCore.setComplianceOptions(javaVersion.effectiveJavaVersionAsString(), options); - options.put(JavaCore.COMPILER_PB_MAX_PER_UNIT, MAXIMUM_ECJ_WARNINGS); - if (shouldIgnoreUnnamedModuleForSplitPackage) { - options.put(JavaCore.COMPILER_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE, "enabled"); - } - if (shouldEnablePreviewFlag(javaVersion)) { - options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); - } - // enabling all supported compiler warnings - EcjDiagnosticProvider.compilerOptions() - .forEach(option -> options.put(option, "warning")); - - astParser.setCompilerOptions(options); - - boolean includeRunningVMBootclasspath = classpath.stream() - .noneMatch(f -> JRE_JARS.contains(f.getName())); - - astParser.setEnvironment(classpath.stream() - .map(File::getAbsolutePath) - .toArray(String[]::new), new String[] {}, new String[] {}, includeRunningVMBootclasspath); + return EcjFrontendBackend.newParser(configuration); + } - astParser.setResolveBindings(true); - astParser.setBindingsRecovery(true); + FrontendBackend backend() { + return new EcjFrontendBackend(this::astParser); + } - return astParser; + final FrontendSession openSession() { + return backend().open(configuration); } @VisibleForTesting @@ -154,45 +120,32 @@ static class Batch extends JParserConfig { public void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action) { - List sourceFilePaths = new ArrayList<>(); - Set notYetAnalyzedFiles = new LinkedHashSet<>(); - List encodings = new ArrayList<>(); - Map inputs = new HashMap<>(); - for (InputFile inputFile : inputFiles) { - String sourceFilePath = inputFile.absolutePath(); - inputs.put(new File(sourceFilePath), inputFile); - sourceFilePaths.add(sourceFilePath); - encodings.add(inputFile.charset().name()); - notYetAnalyzedFiles.add(inputFile); - } + FrontendBatch inputs = FrontendBatch.from(inputFiles); + Set notYetAnalyzedFiles = new LinkedHashSet<>(inputs.inputs()); ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); ProgressMonitor monitor = new ProgressMonitor(isCanceled, analysisProgress); PerformanceMeasure.Duration batchPerformance = PerformanceMeasure.start("ParseAsBatch"); - try { - astParser().createASTs(sourceFilePaths.toArray(new String[0]), encodings.toArray(new String[0]), new String[0], new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit ast) { - PerformanceMeasure.Duration convertDuration = PerformanceMeasure.start("Convert"); - InputFile inputFile = inputs.get(new File(sourceFilePath)); - executionTimeReport.start(inputFile); - Result result; - try { - result = new Result(JParser.convert(javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents(), ast)); - } catch (Exception e) { - result = new Result(e); - } - convertDuration.stop(); - PerformanceMeasure.Duration analyzeDuration = PerformanceMeasure.start("Analyze"); - action.accept(inputFile, result); - - notYetAnalyzedFiles.remove(inputFile); - executionTimeReport.end(); - analyzeDuration.stop(); + try (FrontendSession session = openSession()) { + session.parseBatch(inputs, monitor, (inputFile, unit) -> { + PerformanceMeasure.Duration convertDuration = PerformanceMeasure.start("Convert"); + executionTimeReport.start(inputFile); + Result result; + try { + result = new Result(unit.convert()); + } catch (Exception e) { + result = new Result(e); } - }, monitor); + convertDuration.stop(); + PerformanceMeasure.Duration analyzeDuration = PerformanceMeasure.start("Analyze"); + action.accept(inputFile, result); + + notYetAnalyzedFiles.remove(inputFile); + executionTimeReport.end(); + analyzeDuration.stop(); + }); if (!notYetAnalyzedFiles.isEmpty()) { - String message = String.format("%d/%d files were not analyzed by the batch mode", notYetAnalyzedFiles.size(), sourceFilePaths.size()); + String message = String.format("%d/%d files were not analyzed by the batch mode", notYetAnalyzedFiles.size(), inputs.inputs().size()); throw new AnalysisException(message); } } catch (OperationCanceledException e) { @@ -216,7 +169,9 @@ private void fallbackToFileByFileMode(List inputFiles, BooleanSupplie if (isCanceled.getAsBoolean()) { break; } - FileByFile.parse(astParser(), inputFile, javaVersion, action); + try (FrontendSession session = openSession()) { + FileByFile.parse(session, inputFile, action); + } } } @@ -247,7 +202,9 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa break; } executionTimeReport.start(inputFile); - parse(astParser(), inputFile, javaVersion, action); + try (FrontendSession session = openSession()) { + parse(session, inputFile, action); + } executionTimeReport.end(); progressReport.nextFile(); } @@ -262,11 +219,11 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa } } - private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion javaVersion, BiConsumer action) { + private static void parse(FrontendSession session, InputFile inputFile, BiConsumer action) { Result result; PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); try { - result = new Result(JParser.parse(astParser, javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); + result = new Result(session.parse(inputFile.filename(), inputFile.contents())); } catch (Exception e) { result = new Result(e); } finally { diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSema.java b/java-frontend/src/main/java/org/sonar/java/model/JSema.java index 201cee203f9..0f0cfa11c5d 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSema.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSema.java @@ -40,6 +40,8 @@ public final class JSema implements Sema { private final AST ast; + @Nullable + private Runnable environmentCleaner; final Map declarations = new HashMap<>(); final Map> usages = new HashMap<>(); private final Map types = new HashMap<>(); @@ -50,7 +52,12 @@ public final class JSema implements Sema { private final Map nameToTypeCache = new HashMap<>(); JSema(AST ast) { + this(ast, null); + } + + JSema(AST ast, @Nullable Runnable environmentCleaner) { this.ast = ast; + this.environmentCleaner = environmentCleaner; } Supplier typeSupplier(@Nullable ITypeBinding binding) { @@ -205,7 +212,10 @@ IAnnotationBinding[] resolvePackageAnnotations(String packageName) { @Override public Runnable getEnvironmentCleaner() { - return ASTUtils.getEnvironmentCleaner(ast); + if (environmentCleaner == null) { + environmentCleaner = FrontendResources.idempotent(ASTUtils.getEnvironmentCleaner(ast)); + } + return environmentCleaner; } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/FrontendLifecycleTest.java b/java-frontend/src/test/java/org/sonar/java/model/FrontendLifecycleTest.java new file mode 100644 index 00000000000..bc19ff260b7 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/FrontendLifecycleTest.java @@ -0,0 +1,333 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import org.eclipse.core.runtime.OperationCanceledException; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.java.AnalysisProgress; +import org.sonar.java.ProgressMonitor; +import org.sonar.java.TestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class FrontendLifecycleTest { + + private static final FrontendConfiguration CONFIGURATION = FrontendConfiguration.from(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false); + private static final InputFile INPUT = TestUtils.inputFile("src/test/files/metrics/Classes.java"); + + @Test + void cleanup_is_canonical_idempotent_and_attempts_every_resource() { + List calls = new ArrayList<>(); + RuntimeException first = new IllegalStateException("first"); + RuntimeException second = new IllegalArgumentException("second"); + FrontendResources resources = new FrontendResources(); + Runnable raw = () -> calls.add("once"); + Runnable handle = resources.own(raw); + assertThat(resources.own(raw)).isSameAs(handle).hasSameHashCodeAs(handle); + assertThat(new FrontendResources().own(raw)).isEqualTo(handle); + assertThat(handle).isNotEqualTo(resources.own(() -> calls.add("other"))).isNotNull().isNotEqualTo(raw); + resources.own(() -> { calls.add("first"); throw first; }); + resources.own(() -> { calls.add("second"); throw second; }); + resources.own(() -> calls.add("last")); + handle.run(); + assertThatThrownBy(resources::close).isSameAs(first).hasSuppressedException(second); + resources.close(); + handle.run(); + assertThat(calls).containsExactly("once", "other", "first", "second", "last"); + assertThatThrownBy(() -> resources.own(raw)).isInstanceOf(IllegalStateException.class); + } + + @Test + void cleanup_attempts_every_resource_and_suppresses_errors() { + FrontendResources resources = new FrontendResources(); + AtomicInteger cleaned = new AtomicInteger(); + AssertionError failure = new AssertionError("cleanup"); + IllegalStateException first = new IllegalStateException("first"); + resources.own(() -> { throw first; }); + resources.own(() -> { throw failure; }); + resources.own(cleaned::incrementAndGet); + + assertThatThrownBy(resources::close).isSameAs(first).hasSuppressedException(failure); + resources.close(); + assertThat(cleaned).hasValue(1); + } + + @Test + void cleanup_rethrows_first_error_after_attempting_every_resource() { + FrontendResources resources = new FrontendResources(); + AtomicInteger cleaned = new AtomicInteger(); + AssertionError first = new AssertionError("first"); + IllegalStateException second = new IllegalStateException("second"); + resources.own(() -> { throw first; }); + resources.own(() -> { throw first; }); + resources.own(() -> { throw second; }); + resources.own(cleaned::incrementAndGet); + + assertThatThrownBy(resources::close).isSameAs(first).hasSuppressedException(second); + resources.close(); + assertThat(cleaned).hasValue(1); + } + + @Test + void parse_failure_preserves_primary_and_suppresses_cleanup_failure() { + RuntimeException primary = new IllegalArgumentException("parse"); + RuntimeException secondary = new IllegalStateException("cleanup"); + StubSession session = new StubSession(); + session.failure = primary; + session.own(() -> { throw secondary; }); + assertThatThrownBy(() -> FrontendParser.parse(configuration -> session, CONFIGURATION, "A.java", "")) + .isSameAs(primary).hasSuppressedException(secondary); + assertThat(session.closed).isEqualTo(1); + session.close(); + assertThat(session.closed).isEqualTo(1); + } + + @Test + void parse_failure_suppresses_cleanup_errors() { + StubSession session = new StubSession(); + RuntimeException primary = new IllegalArgumentException("parse"); + session.failure = primary; + AssertionError failure = new AssertionError("cleanup"); + session.own(() -> { throw failure; }); + + assertThatThrownBy(() -> FrontendParser.parse(configuration -> session, CONFIGURATION, "A.java", "")) + .isSameAs(primary).hasSuppressedException(failure); + assertThat(session.closed).isEqualTo(1); + } + + @Test + void parse_errors_close_the_session_before_propagating() { + StubSession session = new StubSession(); + AssertionError failure = new AssertionError("parse"); + session.failure = failure; + try (session) { + assertThatThrownBy(() -> FrontendParser.parse(configuration -> session, CONFIGURATION, "A.java", "")) + .isSameAs(failure).hasNoSuppressedExceptions(); + assertThat(session.closed).isEqualTo(1); + } + assertThat(session.closed).isEqualTo(1); + } + + @Test + void parse_errors_preserve_primary_and_suppress_cleanup_failures() { + for (boolean cleanupError : List.of(false, true)) { + StubSession session = new StubSession(); + AssertionError primary = new AssertionError("parse"); + session.failure = primary; + Throwable secondary = cleanupError ? new AssertionError("cleanup") : new IllegalStateException("cleanup"); + session.own(() -> { + if (secondary instanceof Error error) { + throw error; + } + throw (RuntimeException) secondary; + }); + + assertThatThrownBy(() -> FrontendParser.parse(configuration -> session, CONFIGURATION, "A.java", "")) + .isSameAs(primary).hasSuppressedException(secondary); + assertThat(session.closed).isEqualTo(1); + } + } + + @Test + void conversion_failure_is_delivered_without_losing_session_ownership() { + StubSession session = new StubSession(); + RuntimeException conversion = new IllegalArgumentException("convert"); + session.conversionFailure = conversion; + List outcomes = new ArrayList<>(); + batch(configuration -> session).parse(List.of(INPUT), () -> false, new AnalysisProgress(1), (input, result) -> { + assertThat(session.closed).isZero(); + assertThatThrownBy(result::get).isSameAs(conversion); + outcomes.add(input.filename()); + }); + assertThat(outcomes).containsExactly(INPUT.filename()); + assertThat(session.closed).isEqualTo(1); + } + + @Test + void callback_failure_retries_pending_file_in_fresh_owned_session() { + StubSession first = new StubSession(); + StubSession fallback = new StubSession(); + AtomicInteger opened = new AtomicInteger(); + AtomicInteger callbacks = new AtomicInteger(); + batch(configuration -> opened.getAndIncrement() == 0 ? first : fallback) + .parse(List.of(INPUT), () -> false, new AnalysisProgress(1), (input, result) -> { + if (callbacks.getAndIncrement() == 0) { + throw new IllegalStateException("callback"); + } + assertThat(first.closed).isEqualTo(1); + assertThat(fallback.closed).isZero(); + }); + assertThat(opened).hasValue(2); + assertThat(callbacks).hasValue(2); + assertThat(fallback.closed).isEqualTo(1); + } + + @Test + void cancellation_and_error_before_first_result_close_without_fallback() { + for (Throwable failure : List.of(new OperationCanceledException("cancelled"), new AssertionError("before first result"))) { + StubSession session = new StubSession(); + session.failure = failure; + AtomicInteger opened = new AtomicInteger(); + assertThatThrownBy(() -> batch(configuration -> { opened.incrementAndGet(); return session; }) + .parse(List.of(INPUT), () -> false, new AnalysisProgress(1), (input, result) -> { throw new AssertionError("unexpected callback"); })) + .isSameAs(failure); + assertThat(opened).hasValue(1); + assertThat(session.closed).isEqualTo(1); + } + } + + @Test + void callback_error_preserves_primary_when_close_fails() { + StubSession session = new StubSession(); + Error primary = new AssertionError("callback"); + RuntimeException secondary = new IllegalStateException("close"); + session.own(() -> { throw secondary; }); + assertThatThrownBy(() -> batch(configuration -> session).parse(List.of(INPUT), () -> false, new AnalysisProgress(1), + (input, result) -> { throw primary; })).isSameAs(primary).hasSuppressedException(secondary); + assertThat(session.closed).isEqualTo(1); + } + + @Test + void ecj_registers_environment_before_syntax_conversion_failure() { + FrontendSession session = spy(new EcjFrontendBackend(() -> EcjFrontendBackend.newParser(CONFIGURATION)).open(CONFIGURATION)); + try (session) { + assertThatThrownBy(() -> session.parse("A.java", "class A { void m( }")) + .isInstanceOf(com.sonar.sslr.api.RecognitionException.class); + verify(session).own(any()); + } + } + + @Test + void overlapping_sessions_remain_alive_until_their_own_close() { + StubSession first = new StubSession(); + StubSession second = new StubSession(); + try (FrontendParser.ParsedFile a = FrontendParser.parse(configuration -> first, CONFIGURATION, "A.java", ""); + FrontendParser.ParsedFile b = FrontendParser.parse(configuration -> second, CONFIGURATION, "B.java", "")) { + a.close(); + assertThat(first.closed).isEqualTo(1); + assertThat(second.closed).isZero(); + assertThat(b.tree()).isSameAs(second.tree); + } + assertThat(first.closed).isEqualTo(1); + assertThat(second.closed).isEqualTo(1); + try (FrontendParser.ParsedFile a = FrontendParser.parse(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), "A.java", "class A {}"); + FrontendParser.ParsedFile b = FrontendParser.parse(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), "B.java", "class B { java.util.List value; }")) { + a.close(); + var type = (org.sonar.plugins.java.api.tree.ClassTree) b.tree().types().get(0); + var field = (org.sonar.plugins.java.api.tree.VariableTree) type.members().get(0); + assertThat(field.symbol().type().fullyQualifiedName()).isEqualTo("java.util.List"); + } + } + + @Test + void scanner_cleans_after_visitor_or_modification_failure_and_preserves_primary() { + for (boolean failModification : List.of(false, true)) { + AtomicInteger cleaned = new AtomicInteger(); + org.sonar.java.AnalysisException primary = new org.sonar.java.AnalysisException("scan"); + IllegalStateException secondary = new IllegalStateException("cleanup"); + VisitorsBridge visitor = mock(VisitorsBridge.class); + if (!failModification) { + doThrow(primary).when(visitor).visitFile(any(), org.mockito.ArgumentMatchers.anyBoolean()); + } + var scanner = new org.sonar.java.ast.JavaAstScanner(null, new org.sonar.java.telemetry.NoOpTelemetry(), + org.sonar.java.telemetry.TelemetryKey.JAVA_ANALYSIS_MAIN); + scanner.setVisitorBridge(visitor); + assertThatThrownBy(() -> JParserConfig.Mode.FILE_BY_FILE.create(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of()) + .parse(List.of(INPUT), () -> false, new AnalysisProgress(1), (input, result) -> scanner.simpleScan(input, result, + ast -> { cleaned.incrementAndGet(); throw secondary; }, ast -> { if (failModification) { throw primary; } }))) + .isSameAs(primary).hasSuppressedException(secondary); + assertThat(cleaned).hasValue(1); + } + } + + @Test + void batch_metadata_failures_propagate_before_opening_a_session_or_falling_back() { + for (boolean failPath : List.of(false, true)) { + InputFile input = mock(InputFile.class); + IllegalStateException failure = new IllegalStateException("input metadata"); + if (failPath) { + when(input.absolutePath()).thenThrow(failure); + } else { + when(input.absolutePath()).thenReturn("/tmp/A.java"); + when(input.charset()).thenThrow(failure); + } + AtomicInteger opened = new AtomicInteger(); + assertThatThrownBy(() -> batch(configuration -> { opened.incrementAndGet(); return new StubSession(); }) + .parse(List.of(input), () -> false, new AnalysisProgress(1), (file, result) -> { throw new AssertionError("unexpected callback"); })) + .isSameAs(failure); + assertThat(opened).hasValue(0); + } + } + + private static JParserConfig batch(FrontendBackend backend) { + return new JParserConfig.Batch(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false) { + @Override + FrontendBackend backend() { + return backend; + } + }; + } + + private static final class StubSession extends FrontendSession { + private final JavaTree.CompilationUnitTreeImpl tree = mock(JavaTree.CompilationUnitTreeImpl.class); + private Throwable failure; + private RuntimeException conversionFailure; + private int closed; + + private StubSession() { + own(() -> closed++); + } + + @Override + JavaTree.CompilationUnitTreeImpl parse(String unitName, String source) { + failIfRequested(); + return tree; + } + + @Override + void parseBatch(FrontendBatch inputs, ProgressMonitor monitor, BiConsumer action) { + failIfRequested(); + inputs.inputs().forEach(input -> action.accept(input, () -> { + if (conversionFailure != null) { + throw conversionFailure; + } + return tree; + })); + } + + private void failIfRequested() { + if (failure instanceof RuntimeException exception) { + throw exception; + } + if (failure instanceof Error error) { + throw error; + } + } + } +} From cea1060719a7f4156aea85713f34682a52038297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Mon, 2 Nov 2026 12:01:18 +0100 Subject: [PATCH 2/3] Keep parser test sessions alive through semantic assertions --- .../checks/helpers/ExpressionsHelperTest.java | 2 + .../java/checks/helpers/JParserTestUtils.java | 6 +- .../helpers/ReassignmentFinderTest.java | 2 + .../java/checks/helpers/TreeHelperTest.java | 2 + .../test-classpath-reader/pom.xml | 5 + .../sonar/java/test/TestResourceScope.java | 126 ++++++++++++++++++ .../java/test/TestResourceScopeTest.java | 114 ++++++++++++++++ .../ImportDeclarationOrderCheckTest.java | 2 + .../helpers/ClassPatternsUtilsTest.java | 2 + .../helpers/DeprecatedCheckerHelperTest.java | 2 + .../GeneratedStringLiteralRecognizerTest.java | 2 + .../helpers/IntegerOverflowRangeTest.java | 2 + .../java/checks/helpers/JParserTestUtils.java | 6 +- .../helpers/JavaPropertiesHelperTest.java | 2 + .../java/checks/helpers/JavadocTest.java | 2 + .../checks/helpers/MethodTreeUtilsTest.java | 2 + .../helpers/NullabilityDataUtilsTest.java | 2 + .../checks/helpers/QuickFixHelperTest.java | 2 + .../helpers/ReassignmentFinderTest.java | 2 + .../helpers/SpringContextModelTestUtils.java | 14 +- .../checks/helpers/TryCatchUtilsTest.java | 2 + .../checks/helpers/ValueBasedUtilsTest.java | 2 + .../checks/regex/AbstractRegexCheckTest.java | 2 + .../spring/AmbiguousDependencyCheckTest.java | 2 + ...pringBeansShouldBeAccessibleCheckTest.java | 2 + .../checks/tests/AssertionTypesCheckTest.java | 2 + .../AnnotationFieldReferenceFinderTest.java | 2 + .../org/sonar/java/SonarComponentsTest.java | 2 + .../sonar/java/ast/JavaAstScannerTest.java | 2 + .../java/ast/visitors/AccessorsUtilsTest.java | 2 + .../ast/visitors/ComplexityVisitorTest.java | 2 + .../MethodNestingLevelVisitorTest.java | 2 + .../NumberOfDefinedVariablesVisitorTest.java | 2 + .../ast/visitors/PublicApiCheckerTest.java | 2 + .../java/org/sonar/java/cfg/CFGLoopTest.java | 2 + .../test/java/org/sonar/java/cfg/CFGTest.java | 2 + .../java/org/sonar/java/cfg/CFGUtilsTest.java | 2 + .../org/sonar/java/cfg/LiveVariablesTest.java | 2 + .../java/cfg/VariableReadExtractorTest.java | 2 + .../sonar/java/matcher/TreeMatcherTest.java | 2 + .../java/metrics/MetricsComputerTest.java | 2 + .../DefaultInputFileScannerContextTest.java | 2 + .../DefaultJavaFileScannerContextTest.java | 2 + ...leScannerContextWithSensorContextTest.java | 2 + .../sonar/java/model/ExpressionUtilsTest.java | 2 + .../sonar/java/model/JMethodSymbolTest.java | 13 +- .../java/model/JParserReleasingJarsTest.java | 2 + .../sonar/java/model/JParserSemanticTest.java | 2 + .../org/sonar/java/model/JParserTest.java | 2 + .../sonar/java/model/JParserTestUtils.java | 3 +- .../java/org/sonar/java/model/JSemaTest.java | 20 +-- .../sonar/java/model/JSymbolMetadataTest.java | 11 +- .../org/sonar/java/model/JSymbolTest.java | 2 + .../org/sonar/java/model/JTypeSymbolTest.java | 48 +++---- .../java/org/sonar/java/model/JTypeTest.java | 28 ++-- .../java/org/sonar/java/model/JUtilsTest.java | 2 + .../sonar/java/model/JVariableSymbolTest.java | 15 ++- .../sonar/java/model/JavaTreeModelTest.java | 2 + .../sonar/java/model/KeywordSuperTest.java | 2 + .../org/sonar/java/model/KeywordThisTest.java | 2 + .../org/sonar/java/model/LineUtilsTest.java | 2 + .../sonar/java/model/LiteralUtilsTest.java | 2 + .../sonar/java/model/ModifiersUtilsTest.java | 2 + .../org/sonar/java/model/SemanticFixture.java | 78 +++++++++++ .../sonar/java/model/SemanticFixtureTest.java | 95 +++++++++++++ .../java/model/SemanticIdentityTest.java | 46 +++++++ .../org/sonar/java/model/SymbolsTest.java | 2 + .../java/model/SyntacticEquivalenceTest.java | 2 + .../org/sonar/java/model/TypeUtilsTest.java | 2 + .../sonar/java/model/VisitorsBridgeTest.java | 28 ++-- .../model/declaration/ClassTreeImplTest.java | 2 + .../ExportsDirectiveTreeImplTest.java | 2 + .../model/declaration/MethodTreeImplTest.java | 2 + .../ModuleDeclarationTreeImplTest.java | 2 + .../OpensDirectiveTreeImplTest.java | 2 + .../ProvidesDirectiveTreeImplTest.java | 2 + .../RequiresDirectiveTreeImplTest.java | 2 + .../UsesDirectiveTreeImplTest.java | 2 + .../AssessableExpressionTreeTest.java | 2 + .../expression/InstanceOfTreeImplTest.java | 2 + .../LambdaExpressionTreeImplTest.java | 2 + .../MethodInvocationTreeImplTest.java | 2 + .../pattern/AbstractPatternTreeTest.java | 2 + .../pattern/RecordPatternTreeImplTest.java | 2 + .../BeanDefinitionGathererTest.java | 2 + .../ComponentScanPackageGathererTest.java | 2 + .../SpringContextGathererTest.java | 2 + .../SpringContextModelGathererTest.java | 2 + .../regex/JavaAnalyzerRegexSourceTest.java | 2 + .../org/sonar/java/regex/RegexCacheTest.java | 2 + .../org/sonar/java/regex/RegexCheckTest.java | 2 + .../java/reporting/AnalyzerMessageTest.java | 2 + .../InternalJavaIssueBuilderTest.java | 2 + .../java/reporting/JavaTextEditTest.java | 2 + .../JavaFileScannerContextForTestsTest.java | 2 + .../testing/VisitorsBridgeForTestsTest.java | 2 + .../sonar/java/utils/PackageUtilsTest.java | 2 + .../org/sonar/java/utils/SpringUtilsTest.java | 2 + 98 files changed, 708 insertions(+), 110 deletions(-) create mode 100644 java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java create mode 100644 java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/model/SemanticFixture.java create mode 100644 java-frontend/src/test/java/org/sonar/java/model/SemanticFixtureTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/model/SemanticIdentityTest.java diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java index ebc56962d25..a2e58209074 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java @@ -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; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class ExpressionsHelperTest extends JParserTestUtils { @Test diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index d0e1076e027..2c16a2321fe 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -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 statements) { @@ -77,7 +79,7 @@ public static CompilationUnitTree parse(File file) { public static CompilationUnitTree parse(String source) { List 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(); } } diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java index acf22c22f89..9f0205b51ff 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java @@ -20,6 +20,7 @@ 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.*; @@ -27,6 +28,7 @@ import static java.lang.reflect.Modifier.isPrivate; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ReassignmentFinderTest extends JParserTestUtils { @Test diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/TreeHelperTest.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/TreeHelperTest.java index 056b5f5056b..f32e31f3def 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/TreeHelperTest.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/TreeHelperTest.java @@ -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; @@ -36,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class TreeHelperTest extends JParserTestUtils { @Test diff --git a/java-checks-test-sources/test-classpath-reader/pom.xml b/java-checks-test-sources/test-classpath-reader/pom.xml index 1c3446bab4a..d8e1eb7cc28 100644 --- a/java-checks-test-sources/test-classpath-reader/pom.xml +++ b/java-checks-test-sources/test-classpath-reader/pom.xml @@ -14,6 +14,11 @@ Code Analyzer for Java :: Test Classpath Reader + + org.junit.jupiter + junit-jupiter-api + provided + com.google.code.findbugs jsr305 diff --git a/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java b/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java new file mode 100644 index 00000000000..2c8134d7163 --- /dev/null +++ b/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java @@ -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> 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 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 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; + } + } + } +} diff --git a/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java b/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java new file mode 100644 index 00000000000..fa0f4035063 --- /dev/null +++ b/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java @@ -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 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 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 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 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 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); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java index a464ed808d2..e7f47922a1d 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java @@ -25,6 +25,7 @@ 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; @@ -32,6 +33,7 @@ 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") diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/ClassPatternsUtilsTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/ClassPatternsUtilsTest.java index e195948572a..95d5ad552ed 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/ClassPatternsUtilsTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/ClassPatternsUtilsTest.java @@ -18,6 +18,7 @@ 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; @@ -25,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +@TestResourceScope.Scoped class ClassPatternsUtilsTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/DeprecatedCheckerHelperTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/DeprecatedCheckerHelperTest.java index a945a056ba3..3d790c4be84 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/DeprecatedCheckerHelperTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/DeprecatedCheckerHelperTest.java @@ -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 diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/GeneratedStringLiteralRecognizerTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/GeneratedStringLiteralRecognizerTest.java index 6a5cc4e18af..f2f55c829e4 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/GeneratedStringLiteralRecognizerTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/GeneratedStringLiteralRecognizerTest.java @@ -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 diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/IntegerOverflowRangeTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/IntegerOverflowRangeTest.java index 00abe83a526..6c63ff36ae7 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/IntegerOverflowRangeTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/IntegerOverflowRangeTest.java @@ -21,6 +21,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.checks.helpers.IntegerOverflowRange.Range; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.ExpressionTree; @@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class IntegerOverflowRangeTest { private static final BigInteger INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE); diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index fd512787b13..6687f496c50 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -22,8 +22,9 @@ import java.util.Arrays; 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.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.ClassTree; @@ -37,6 +38,7 @@ import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.VariableTree; +@TestResourceScope.Scoped public abstract class JParserTestUtils { static IdentifierTree variableFromLastReturnStatement(List statements) { @@ -85,7 +87,7 @@ public static CompilationUnitTree parse(File file) { public static CompilationUnitTree parse(String source) { List 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(); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/JavaPropertiesHelperTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/JavaPropertiesHelperTest.java index 59b8432ccd3..843c56e6ea3 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/JavaPropertiesHelperTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/JavaPropertiesHelperTest.java @@ -17,6 +17,7 @@ package org.sonar.java.checks.helpers; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; @@ -30,6 +31,7 @@ import java.lang.reflect.Constructor; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class JavaPropertiesHelperTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/JavadocTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/JavadocTest.java index a3dd9eee31e..b076e070a17 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/JavadocTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/JavadocTest.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class JavadocTest { private static CompilationUnitTree tree; private static Javadoc fooJavadoc; diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/MethodTreeUtilsTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/MethodTreeUtilsTest.java index f1ced12fd27..ef8d9409e54 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/MethodTreeUtilsTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/MethodTreeUtilsTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JavaVersionImpl; import org.sonar.java.model.expression.LiteralTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; @@ -43,6 +44,7 @@ import static org.sonar.java.checks.helpers.MethodTreeUtils.lamdaArgumentAt; import static org.sonar.java.checks.helpers.MethodTreeUtils.parentMethodInvocationOfArgumentAtPos; +@TestResourceScope.Scoped class MethodTreeUtilsTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/NullabilityDataUtilsTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/NullabilityDataUtilsTest.java index 4633f3bb81c..24e5fc92dfa 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/NullabilityDataUtilsTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/NullabilityDataUtilsTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JavaTree; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.VariableTree; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.checks.helpers.NullabilityDataUtils.nullabilityAsString; +@TestResourceScope.Scoped class NullabilityDataUtilsTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/QuickFixHelperTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/QuickFixHelperTest.java index 0deff7b18e9..98c85a7bb3e 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/QuickFixHelperTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/QuickFixHelperTest.java @@ -31,6 +31,7 @@ import org.sonar.java.model.InternalSyntaxToken; import org.sonar.java.reporting.JavaQuickFix; import org.sonar.java.reporting.JavaTextEdit; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.ClassTree; @@ -48,6 +49,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class QuickFixHelperTest { private static void requireSingleParameter(ParameterDeclarations parameterDeclarations) { diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java index 9c4d2fa995c..35a77dbac73 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java @@ -19,6 +19,7 @@ import java.lang.reflect.Constructor; 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.BlockTree; import org.sonar.plugins.java.api.tree.ClassTree; @@ -37,6 +38,7 @@ import static java.lang.reflect.Modifier.isPrivate; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ReassignmentFinderTest extends JParserTestUtils { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java index e5c715f9739..836eb360ad0 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java @@ -28,7 +28,7 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.java.SonarComponents; import org.sonar.java.checks.verifier.TestUtils; -import org.sonar.java.model.JParser; +import org.sonar.java.model.FrontendParser; import org.sonar.java.model.JParserConfig; import org.sonar.java.model.VisitorsBridge; import org.sonar.java.model.springcontext.BeanDefinitionGatherer; @@ -44,7 +44,6 @@ import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaVersion; -import org.sonar.plugins.java.api.tree.CompilationUnitTree; /** * Builds a {@link SpringContextModel} for the tests of rules that consume one instead of visiting an AST, either @@ -145,9 +144,10 @@ private static SpringContextModel buildModel(List filePaths) { VisitorsBridge visitorsBridge = new VisitorsBridge(gatherers, classpath, sonarComponents); for (String filePath : filePaths) { File file = new File(filePath); - CompilationUnitTree compilationUnit = parse(file, classpath); - visitorsBridge.setCurrentFile(inputFile(file)); - visitorsBridge.visitFile(compilationUnit, false); + try (FrontendParser.ParsedFile parsed = parse(file, classpath)) { + visitorsBridge.setCurrentFile(inputFile(file)); + visitorsBridge.visitFile(parsed.tree(), false); + } } visitorsBridge.endOfAnalysis(); return SpringContextModel.of(gatheringModel); @@ -166,7 +166,7 @@ private static InputFile inputFile(File file) { } } - private static CompilationUnitTree parse(File file, List classpath) { + private static FrontendParser.ParsedFile parse(File file, List classpath) { String source; try { source = Files.readString(file.toPath(), StandardCharsets.UTF_8); @@ -174,6 +174,6 @@ private static CompilationUnitTree parse(File file, List classpath) { throw new IllegalStateException("Unable to read file '" + file.getAbsolutePath() + "'", e); } JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), file.getName(), source); + return FrontendParser.parse(version, classpath, file.getName(), source); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/TryCatchUtilsTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/TryCatchUtilsTest.java index 12c542bb99e..24720c887b7 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/TryCatchUtilsTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/TryCatchUtilsTest.java @@ -17,6 +17,7 @@ package org.sonar.java.checks.helpers; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class TryCatchUtilsTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/ValueBasedUtilsTest.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/ValueBasedUtilsTest.java index f5f4f1238ef..24748927e06 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/ValueBasedUtilsTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/ValueBasedUtilsTest.java @@ -17,6 +17,7 @@ package org.sonar.java.checks.helpers; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.SyntaxTrivia; @@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ValueBasedUtilsTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/regex/AbstractRegexCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/regex/AbstractRegexCheckTest.java index 33e9585c4d7..d5880868c97 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/regex/AbstractRegexCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/regex/AbstractRegexCheckTest.java @@ -27,6 +27,7 @@ import org.sonar.check.Rule; import org.sonar.java.checks.helpers.JParserTestUtils; import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.java.test.TestResourceScope; import org.sonarsource.analyzer.commons.regex.RegexParseResult; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.ClassTree; @@ -46,6 +47,7 @@ import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; import static org.sonar.java.checks.regex.AbstractRegexCheck.getLiterals; +@TestResourceScope.Scoped class AbstractRegexCheckTest { private static final String JAVA_CODE = """ diff --git a/java-checks/src/test/java/org/sonar/java/checks/spring/AmbiguousDependencyCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/spring/AmbiguousDependencyCheckTest.java index 2927a17d7c0..55d7bd2bb0a 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/spring/AmbiguousDependencyCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/spring/AmbiguousDependencyCheckTest.java @@ -26,6 +26,7 @@ import org.sonar.java.model.springcontext.BeanDefinitionHolder; import org.sonar.java.model.springcontext.ProfileExpression; import org.sonar.java.model.springcontext.SpringContextModel; +import org.sonar.java.test.TestResourceScope; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.beanLocation; @@ -33,6 +34,7 @@ import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.dummyInputFile; import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.indexBeanByType; +@TestResourceScope.Scoped class AmbiguousDependencyCheckTest { private static final String BASE_PATH = "checks/spring/s9352/"; diff --git a/java-checks/src/test/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheckTest.java index 45ab07145bb..ee31751ee96 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheckTest.java @@ -24,6 +24,7 @@ import org.sonar.java.model.springcontext.BeanLocation; import org.sonar.java.model.springcontext.SpringContextModel; import org.sonar.java.reporting.AnalyzerMessage; +import org.sonar.java.test.TestResourceScope; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.beanLocation; @@ -31,6 +32,7 @@ import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.dummyInputFile; import static org.sonar.java.checks.helpers.SpringContextModelTestUtils.registerBean; +@TestResourceScope.Scoped class SpringBeansShouldBeAccessibleCheckTest { private static final String BASE_PATH = "checks/spring/s4605/"; diff --git a/java-checks/src/test/java/org/sonar/java/checks/tests/AssertionTypesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/tests/AssertionTypesCheckTest.java index 5a91782436e..08f3562d7f3 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/tests/AssertionTypesCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/tests/AssertionTypesCheckTest.java @@ -20,6 +20,7 @@ import org.sonar.java.checks.helpers.JParserTestUtils; import org.sonar.java.checks.tests.AssertionTypesCheck.Argument; import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; @@ -31,6 +32,7 @@ import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; import static org.sonar.java.checks.verifier.TestUtils.testCodeSourcesPath; +@TestResourceScope.Scoped class AssertionTypesCheckTest { @Test diff --git a/java-checks/src/test/java/org/sonar/java/checks/unused/utils/AnnotationFieldReferenceFinderTest.java b/java-checks/src/test/java/org/sonar/java/checks/unused/utils/AnnotationFieldReferenceFinderTest.java index 0f89fe0f831..8e0f20a9102 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/unused/utils/AnnotationFieldReferenceFinderTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/unused/utils/AnnotationFieldReferenceFinderTest.java @@ -24,6 +24,7 @@ import org.sonar.java.checks.helpers.JParserTestUtils; import org.sonar.java.model.JavaTree; import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.VariableTree; @@ -33,6 +34,7 @@ * Tests an edge case of {@link AnnotationFieldReferenceFinder} that can not be covered with regular Java files by the tests in * {@link org.sonar.java.checks.unused.UnusedPrivateFieldCheckTest}. */ +@TestResourceScope.Scoped class AnnotationFieldReferenceFinderTest { @RegisterExtension private final LogTesterJUnit5 logTester = new LogTesterJUnit5().setLevel(Level.ERROR); diff --git a/java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java b/java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java index dbd2713a525..8bc1a02f233 100644 --- a/java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java @@ -83,6 +83,7 @@ import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.JavaTree; import org.sonar.java.reporting.AnalyzerMessage; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.CheckRegistrar; import org.sonar.plugins.java.api.JavaCheck; @@ -117,6 +118,7 @@ import static org.sonar.java.TestUtils.computeLineEndOffsets; @ExtendWith(MockitoExtension.class) +@TestResourceScope.Scoped class SonarComponentsTest { private static final Version V8_9 = Version.create(8, 9); diff --git a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java index 33339029e15..935e20404f7 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java @@ -57,6 +57,7 @@ import org.sonar.java.notchecks.VisitorNotInChecksPackage; import org.sonar.java.telemetry.NoOpTelemetry; import org.sonar.java.telemetry.TelemetryKey; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; @@ -82,6 +83,7 @@ import static org.mockito.Mockito.when; import static org.sonar.java.model.EcjTestSupport.clearSymbol; +@TestResourceScope.Scoped class JavaAstScannerTest { public static final InputFile TRIVIAL_COMPILATION_UNIT = TestUtils.inputFile("src/test/resources/AstScannerNoParseError.txt"); diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/AccessorsUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/AccessorsUtilsTest.java index b38e862d82c..3f972bec6bd 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/AccessorsUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/AccessorsUtilsTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class AccessorsUtilsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/ComplexityVisitorTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/ComplexityVisitorTest.java index 4fceb50994d..e838fdcdb56 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/ComplexityVisitorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/ComplexityVisitorTest.java @@ -19,6 +19,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionTree; @@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ComplexityVisitorTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/MethodNestingLevelVisitorTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/MethodNestingLevelVisitorTest.java index ab4b5109d0e..02220495fac 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/MethodNestingLevelVisitorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/MethodNestingLevelVisitorTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -26,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class MethodNestingLevelVisitorTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/NumberOfDefinedVariablesVisitorTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/NumberOfDefinedVariablesVisitorTest.java index 04c6022f69e..14d1d7c2cfb 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/NumberOfDefinedVariablesVisitorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/NumberOfDefinedVariablesVisitorTest.java @@ -18,12 +18,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class NumberOfDefinedVariablesVisitorTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/PublicApiCheckerTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/PublicApiCheckerTest.java index 6ecd8fa3cc4..d27c956bf19 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/PublicApiCheckerTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/PublicApiCheckerTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.IdentifierTree; @@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class PublicApiCheckerTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/cfg/CFGLoopTest.java b/java-frontend/src/test/java/org/sonar/java/cfg/CFGLoopTest.java index e03e71eadec..870de0470d4 100644 --- a/java-frontend/src/test/java/org/sonar/java/cfg/CFGLoopTest.java +++ b/java-frontend/src/test/java/org/sonar/java/cfg/CFGLoopTest.java @@ -19,6 +19,7 @@ import java.util.Comparator; import org.junit.jupiter.api.Test; import org.sonar.java.cfg.CFG.Block; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.Tree; import java.util.ArrayList; @@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class CFGLoopTest { private static final CFGTestLoader loader = new CFGTestLoader("src/test/files/cfg/CFGLoopTest.java"); diff --git a/java-frontend/src/test/java/org/sonar/java/cfg/CFGTest.java b/java-frontend/src/test/java/org/sonar/java/cfg/CFGTest.java index 543bd51f429..6998fc0230f 100644 --- a/java-frontend/src/test/java/org/sonar/java/cfg/CFGTest.java +++ b/java-frontend/src/test/java/org/sonar/java/cfg/CFGTest.java @@ -29,6 +29,7 @@ import org.sonar.java.cfg.CFG.Block; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.LiteralUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -101,6 +102,7 @@ import static org.sonar.plugins.java.api.tree.Tree.Kind.WHILE_STATEMENT; import static org.sonar.plugins.java.api.tree.Tree.Kind.YIELD_STATEMENT; +@TestResourceScope.Scoped class CFGTest { @RegisterExtension diff --git a/java-frontend/src/test/java/org/sonar/java/cfg/CFGUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/cfg/CFGUtilsTest.java index 35e33618dbe..ada56fcb46c 100644 --- a/java-frontend/src/test/java/org/sonar/java/cfg/CFGUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/cfg/CFGUtilsTest.java @@ -18,12 +18,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.cfg.CFG.Block; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.Tree; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class CFGUtilsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/cfg/LiveVariablesTest.java b/java-frontend/src/test/java/org/sonar/java/cfg/LiveVariablesTest.java index 47885c13347..59fc334f9f7 100644 --- a/java-frontend/src/test/java/org/sonar/java/cfg/LiveVariablesTest.java +++ b/java-frontend/src/test/java/org/sonar/java/cfg/LiveVariablesTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -27,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class LiveVariablesTest { private static CFG buildCFG(String methodCode) { diff --git a/java-frontend/src/test/java/org/sonar/java/cfg/VariableReadExtractorTest.java b/java-frontend/src/test/java/org/sonar/java/cfg/VariableReadExtractorTest.java index 832111d303f..fb53fc6ee6c 100644 --- a/java-frontend/src/test/java/org/sonar/java/cfg/VariableReadExtractorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/cfg/VariableReadExtractorTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class VariableReadExtractorTest { private static MethodTree buildMethodTree(String methodCode) { diff --git a/java-frontend/src/test/java/org/sonar/java/matcher/TreeMatcherTest.java b/java-frontend/src/test/java/org/sonar/java/matcher/TreeMatcherTest.java index 2b87ca16ebb..cc9a724069a 100644 --- a/java-frontend/src/test/java/org/sonar/java/matcher/TreeMatcherTest.java +++ b/java-frontend/src/test/java/org/sonar/java/matcher/TreeMatcherTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.BlockTree; @@ -53,6 +54,7 @@ import static org.sonar.java.matcher.TreeMatcher.withBody; import static org.sonar.java.matcher.TreeMatcher.withExpression; +@TestResourceScope.Scoped class TreeMatcherTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/metrics/MetricsComputerTest.java b/java-frontend/src/test/java/org/sonar/java/metrics/MetricsComputerTest.java index b6970188986..3948cc37bd4 100644 --- a/java-frontend/src/test/java/org/sonar/java/metrics/MetricsComputerTest.java +++ b/java-frontend/src/test/java/org/sonar/java/metrics/MetricsComputerTest.java @@ -18,12 +18,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.MethodTree; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class MetricsComputerTest implements MetricsScannerContext { MetricsComputer mc = new MetricsComputer(); diff --git a/java-frontend/src/test/java/org/sonar/java/model/DefaultInputFileScannerContextTest.java b/java-frontend/src/test/java/org/sonar/java/model/DefaultInputFileScannerContextTest.java index e5d137c97fc..93ccb881910 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/DefaultInputFileScannerContextTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/DefaultInputFileScannerContextTest.java @@ -26,6 +26,7 @@ import org.sonar.java.SonarComponents; import org.sonar.java.TestUtils; import org.sonar.java.reporting.AnalyzerMessage; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.ModuleScannerContext; import org.sonar.plugins.java.api.internal.EndOfAnalysis; @@ -41,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class DefaultInputFileScannerContextTest { protected static final File JAVA_FILE = new File("src/test/files/api/JavaFileScannerContext.java"); diff --git a/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextTest.java b/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextTest.java index 9bc2430d3c1..83f54033aef 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextTest.java @@ -26,6 +26,7 @@ import org.sonar.java.reporting.AnalyzerMessage; import org.sonar.java.reporting.AnalyzerMessage.TextSpan; import org.sonar.java.reporting.FluentReporting; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.JavaFileScannerContext.Location; import org.sonar.plugins.java.api.SourceMap; @@ -44,6 +45,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class DefaultJavaFileScannerContextTest extends DefaultInputFileScannerContextTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextWithSensorContextTest.java b/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextWithSensorContextTest.java index 501bd6d2e56..635a69a867e 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextWithSensorContextTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/DefaultJavaFileScannerContextWithSensorContextTest.java @@ -40,6 +40,7 @@ import org.sonar.java.TestUtils; import org.sonar.java.classpath.ClasspathForMain; import org.sonar.java.classpath.ClasspathForTest; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -56,6 +57,7 @@ * */ @ExtendWith(MockitoExtension.class) +@TestResourceScope.Scoped class DefaultJavaFileScannerContextWithSensorContextTest { @Mock private FileLinesContextFactory fileLinesContextFactory; diff --git a/java-frontend/src/test/java/org/sonar/java/model/ExpressionUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/ExpressionUtilsTest.java index e00b3e2eb9c..8ae8aa7eb26 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/ExpressionUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/ExpressionUtilsTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.sonar.java.model.expression.ParenthesizedTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; @@ -57,6 +58,7 @@ import static org.sonar.java.model.ExpressionUtils.skipParenthesesUpwards; import static org.sonar.java.model.assertions.TreeAssert.assertThat; +@TestResourceScope.Scoped class ExpressionUtilsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java index 6f0595798fd..bae9e77a15e 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java @@ -35,6 +35,7 @@ import org.sonar.java.model.expression.NewClassTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; @@ -54,11 +55,11 @@ import static org.mockito.Mockito.when; import static org.sonar.java.model.EcjTestSupport.methodBinding; import static org.sonar.java.model.EcjTestSupport.sema; -import static org.sonar.java.model.EcjTestSupport.typeBinding; import static org.sonar.java.model.JUtilsTest.firstClass; import static org.sonar.java.model.JUtilsTest.firstMethod; import static org.sonar.java.model.JUtilsTest.nthMethod; +@TestResourceScope.Scoped class JMethodSymbolTest { @Test @@ -66,15 +67,15 @@ void test() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C m(C p) throws Exception { return null; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = sema(cu).methodSymbol(Objects.requireNonNull(methodBinding(m))); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.parameterTypes()) - .containsOnly(sema(cu).type(Objects.requireNonNull(typeBinding(c)))); + .containsOnly(c.symbol().type()); assertThat(symbol.declarationParameters()) .containsExactly(m.parameters().get(0).symbol()); assertThat(symbol.returnType()) - .isSameAs(sema(cu).typeSymbol(Objects.requireNonNull(typeBinding(c)))); + .isSameAs(c.symbol()); assertThat(symbol.thrownTypes()) - .containsOnly(sema(cu).type(Objects.requireNonNull(sema(cu).resolveType("java.lang.Exception")))); + .containsOnly(cu.sema.getClassType("java.lang.Exception")); } @Test @@ -83,7 +84,7 @@ void test_with_generics() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); Symbol.MethodSymbol declarationSymbol = m.symbol(); - JType declarationParameterType = sema(cu).type(Objects.requireNonNull((methodBinding(m).getParameterTypes()[0]))); + Type declarationParameterType = m.parameters().get(0).type().symbolType(); assertThat(declarationSymbol.parameterTypes()) .containsOnly(declarationParameterType); Symbol declarationParameterSymbol = m.parameters().get(0).symbol(); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java index e9fb5846280..a927af0b0a0 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; @@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class JParserReleasingJarsTest { @TempDir diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java index 1c9e79b9aa1..e4e6822f61d 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java @@ -59,6 +59,7 @@ import org.sonar.java.model.statement.StaticInitializerTreeImpl; import org.sonar.java.model.statement.SwitchExpressionTreeImpl; import org.sonar.java.model.statement.YieldStatementTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.location.Position; import org.sonar.plugins.java.api.semantic.Symbol; @@ -109,6 +110,7 @@ import static org.sonar.java.model.assertions.TreeAssert.assertThat; import static org.sonar.java.model.assertions.TypeAssert.assertThat; +@TestResourceScope.Scoped class JParserSemanticTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java index 0ac663b4bcb..ca1818ce8b3 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java @@ -59,6 +59,7 @@ import org.sonar.java.TestUtils; import org.sonar.java.model.JavaTree.CompilationUnitTreeImpl; import org.sonar.java.model.declaration.ClassTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.location.Range; @@ -106,6 +107,7 @@ import static org.sonar.java.model.JParserTestUtils.DEFAULT_CLASSPATH; import static org.sonar.java.model.JParserTestUtils.parse; +@TestResourceScope.Scoped class JParserTest { @RegisterExtension diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java index 5224a7e09ec..97d0ca78696 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -81,7 +82,7 @@ public static CompilationUnitTree parse(String unitName, String source, List classpath, JavaVersion version) { - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), unitName, source); + return TestResourceScope.retain(FrontendParser.parse(version, classpath, unitName, source)).tree(); } public static List checksTestClassPath() throws IOException { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java index ed51a20a6a9..1b524ad8858 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java @@ -17,17 +17,16 @@ package org.sonar.java.model; import java.util.Objects; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Type; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +@TestResourceScope.Scoped class JSemaTest { @Test @@ -77,20 +76,7 @@ void resolvePackageAnnotations() { @BeforeEach void setup() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - JavaCore.setComplianceOptions(version, JavaCore.getOptions()); - astParser.setEnvironment( - new String[]{"target/test-classes"}, - new String[]{}, - new String[]{}, - true - ); - astParser.setResolveBindings(true); - astParser.setUnitName("File.java"); - astParser.setSource("".toCharArray()); - AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + sema = EcjTestSupport.sema(SemanticFixture.scoped("", java.util.List.of(new java.io.File("target/test-classes"))).tree()); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java index 38584ff963a..20623785d87 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java @@ -30,6 +30,7 @@ import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.expression.MethodInvocationTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; @@ -55,6 +56,7 @@ import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityType.UNKNOWN; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityType.WEAK_NULLABLE; +@TestResourceScope.Scoped class JSymbolMetadataTest { private static final Path NULLABILITY_SOURCE_DIR = JParserTestUtils.CHECKS_TEST_DIR @@ -83,12 +85,11 @@ void flattened_annotations_are_independent_of_exposed_metadata_collections() { @Test void should_convert_annotation_values() { - JavaTree.CompilationUnitTreeImpl cu = test("import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @interface A { }"); - ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - Object[] value = (Object[]) c.symbol().metadata().annotations().get(0).values().get(0).value(); + SemanticFixture fixture = SemanticFixture.scoped("import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @interface A { }"); + Object[] value = (Object[]) fixture.annotations(fixture.declaration(0).symbol()).get(0).values().get(0).value(); assertThat(value).hasSize(2); - assertThat(((JVariableSymbol) value[0]).isEnum()).isTrue(); - assertThat(((JVariableSymbol) value[1]).isEnum()).isTrue(); + assertThat(((Symbol.VariableSymbol) value[0]).isEnum()).isTrue(); + assertThat(((Symbol.VariableSymbol) value[1]).isEnum()).isTrue(); } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java index 64291977a35..bcf309e0e90 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java @@ -29,6 +29,7 @@ import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.statement.BlockTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -57,6 +58,7 @@ import static org.sonar.java.model.EcjTestSupport.variableBinding; import static org.sonar.java.model.assertions.SymbolAssert.assertThat; +@TestResourceScope.Scoped class JSymbolTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java index 654cbfada69..9c6ea3865d1 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java @@ -22,6 +22,7 @@ import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Type; import static org.assertj.core.api.Assertions.assertThat; @@ -33,35 +34,22 @@ import static org.sonar.java.model.EcjTestSupport.typeBinding; import static org.sonar.java.model.EcjTestSupport.variableBinding; +@TestResourceScope.Scoped class JTypeSymbolTest { @Test void superClass() { - JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); - ITypeBinding javaLangObject = Objects.requireNonNull(sema(cu).resolveType("java.lang.Object")); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - ITypeBinding c2 = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(2)))); + SemanticFixture fixture = SemanticFixture.scoped("interface I { } class C implements I { } class C2 extends C { }"); + Type javaLangObject = fixture.type("java.lang.Object"); + var i = fixture.declaration(0).symbol(); + var c = fixture.declaration(1).symbol(); + var c2 = fixture.declaration(2).symbol(); assertAll( - // for java.lang.Object - () -> - assertThat(sema(cu).typeSymbol(javaLangObject).superClass()) - .isNull(), - // for interfaces - () -> - assertThat(sema(cu).typeSymbol(i).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - // for classes - () -> - assertThat(sema(cu).typeSymbol(c).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - () -> - assertThat(sema(cu).typeSymbol(c2).superClass()) - .isSameAs(sema(cu).type(c)), - // for arrays - () -> - assertThat(sema(cu).typeSymbol(javaLangObject.createArrayType(1)).superClass()) - .isSameAs(sema(cu).type(javaLangObject)) + () -> assertThat(javaLangObject.symbol().superClass()).isNull(), + () -> assertThat(i.superClass()).isSameAs(javaLangObject), + () -> assertThat(c.superClass()).isSameAs(javaLangObject), + () -> assertThat(c2.superClass()).isSameAs(c.type()), + () -> assertThat(fixture.type("java.lang.Object[]").symbol().superClass()).isSameAs(javaLangObject) ); } @@ -78,13 +66,11 @@ void testSuperClassWhenSemanticCanNotResolveObjectType() { @Test void interfaces() { - JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { }"); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - assertThat(sema(cu).typeSymbol(i).interfaces()) - .isEmpty(); - assertThat(sema(cu).typeSymbol(c).interfaces()) - .containsOnly(sema(cu).type(i)); + SemanticFixture fixture = SemanticFixture.scoped("interface I { } class C implements I { }"); + var i = fixture.declaration(0).symbol(); + var c = fixture.declaration(1).symbol(); + assertThat(i.interfaces()).isEmpty(); + assertThat(c.interfaces()).containsOnly(i.type()); } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java index 45352af96a1..979f2fc0802 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java @@ -18,8 +18,6 @@ import java.util.Objects; import java.util.stream.Stream; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,6 +32,7 @@ import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.semantic.Type; @@ -46,6 +45,7 @@ import static org.sonar.java.model.EcjTestSupport.typeBinding; import static org.sonar.java.model.assertions.TypeAssert.assertThat; +@TestResourceScope.Scoped class JTypeTest { @RegisterExtension @@ -276,7 +276,7 @@ void erasure() { @Test void elementType() { - assertThat(type("int[][]").elementType()) + assertThat(((Type.ArrayType) type("int[][]")).elementType()) .isSameAs(type("int[]")); } @@ -327,7 +327,7 @@ void null_type() { @Test void is_subtype_of_should_not_throw_NPE() { - JType objectType = type("java.lang.Object"); + Type objectType = type("java.lang.Object"); ITypeBinding brokenStringBinding = spy(Objects.requireNonNull(sema.resolveType("java.lang.String"))); // simulate the NullPointerException described in SONARJAVA-4390 when(brokenStringBinding.isSubTypeCompatible(any())) @@ -363,27 +363,17 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); } - private JType type(String name) { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType(name)); - return sema.type(typeBinding); + private Type type(String name) { + return fixture.type(name); } private JSema sema; + private SemanticFixture fixture; @BeforeEach void setup() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - astParser.setEnvironment( - new String[]{}, - new String[]{}, - new String[]{}, - true - ); - astParser.setResolveBindings(true); - astParser.setUnitName("File.java"); - astParser.setSource("".toCharArray()); - AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + fixture = SemanticFixture.scoped("", java.util.List.of()); + sema = sema(fixture.tree()); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java index 16496a21611..0b73a544754 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java @@ -28,6 +28,7 @@ import org.sonar.java.model.expression.TypeCastExpressionTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; @@ -46,6 +47,7 @@ import static org.sonar.java.model.EcjTestSupport.typeBinding; import static org.sonar.java.model.EcjTestSupport.variableBinding; +@TestResourceScope.Scoped class JUtilsTest { static final JSema SEMA = sema(test("")); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java index cf569dcfa8a..c8759237c00 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java @@ -20,15 +20,16 @@ import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.java.test.TestResourceScope; +import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.BlockTree; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.VariableTree; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.model.EcjTestSupport.sema; -import static org.sonar.java.model.EcjTestSupport.variableBinding; +@TestResourceScope.Scoped class JVariableSymbolTest { @Test @@ -37,10 +38,10 @@ void isLocalVariable() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(0); VariableTreeImpl localVariable = (VariableTreeImpl) method.block().body().get(0); - JVariableSymbol variableSymbol = sema(cu).variableSymbol(variableBinding(localVariable)); + Symbol variableSymbol = localVariable.symbol(); assertThat(variableSymbol.isLocalVariable()).isTrue(); VariableTreeImpl classVariable = (VariableTreeImpl) c.members().get(1); - variableSymbol = sema(cu).variableSymbol(variableBinding(classVariable)); + variableSymbol = classVariable.symbol(); assertThat(variableSymbol.isLocalVariable()).isFalse(); } @@ -63,15 +64,15 @@ void isParameter() { MethodTreeImpl method = (MethodTreeImpl) c.members().get(0); VariableTreeImpl localVariable = (VariableTreeImpl) method.block().body().get(0); - JVariableSymbol variableSymbol = sema(cu).variableSymbol(variableBinding(localVariable)); + Symbol variableSymbol = localVariable.symbol(); assertThat(variableSymbol.isParameter()).isFalse(); VariableTreeImpl parameter = (VariableTreeImpl) method.parameters().get(0); - variableSymbol = sema(cu).variableSymbol(variableBinding(parameter)); + variableSymbol = parameter.symbol(); assertThat(variableSymbol.isParameter()).isTrue(); } private static JavaTree.CompilationUnitTreeImpl test(String source) { - return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); + return SemanticFixture.scoped(source).tree(); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JavaTreeModelTest.java b/java-frontend/src/test/java/org/sonar/java/model/JavaTreeModelTest.java index 45b813fc693..926c7b3012d 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JavaTreeModelTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JavaTreeModelTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.sonar.java.ast.parser.TypeParameterListTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.AnnotationTree; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree; @@ -88,6 +89,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.model.assertions.TreeAssert.assertThat; +@TestResourceScope.Scoped class JavaTreeModelTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/KeywordSuperTest.java b/java-frontend/src/test/java/org/sonar/java/model/KeywordSuperTest.java index f1ef8a10b4c..776bc2be775 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/KeywordSuperTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/KeywordSuperTest.java @@ -17,6 +17,7 @@ package org.sonar.java.model; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; @@ -26,6 +27,7 @@ import static org.sonar.java.model.EcjTestSupport.clearSymbol; import static org.sonar.java.model.EcjTestSupport.clearType; +@TestResourceScope.Scoped class KeywordSuperTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/KeywordThisTest.java b/java-frontend/src/test/java/org/sonar/java/model/KeywordThisTest.java index c9a9d639565..8d634104562 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/KeywordThisTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/KeywordThisTest.java @@ -17,6 +17,7 @@ package org.sonar.java.model; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; @@ -26,6 +27,7 @@ import static org.sonar.java.model.EcjTestSupport.clearSymbol; import static org.sonar.java.model.EcjTestSupport.clearType; +@TestResourceScope.Scoped class KeywordThisTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/LineUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/LineUtilsTest.java index abdf7a2673b..8a8c2845fab 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/LineUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/LineUtilsTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.SyntaxToken; @@ -28,6 +29,7 @@ import static org.sonar.java.model.LineUtils.splitLines; import static org.sonar.java.model.LineUtils.startLine; +@TestResourceScope.Scoped class LineUtilsTest { private static CompilationUnitTree tree; diff --git a/java-frontend/src/test/java/org/sonar/java/model/LiteralUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/LiteralUtilsTest.java index 24f17cad1ab..6e80891fc13 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/LiteralUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/LiteralUtilsTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class LiteralUtilsTest { static List variables; diff --git a/java-frontend/src/test/java/org/sonar/java/model/ModifiersUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/ModifiersUtilsTest.java index 4126d8e4bf3..d2e22fc0f92 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/ModifiersUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/ModifiersUtilsTest.java @@ -19,6 +19,7 @@ import java.io.File; import java.lang.reflect.Constructor; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.Modifier; @@ -29,6 +30,7 @@ import static java.lang.reflect.Modifier.isPrivate; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ModifiersUtilsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/SemanticFixture.java b/java-frontend/src/test/java/org/sonar/java/model/SemanticFixture.java new file mode 100644 index 00000000000..09b580fed9b --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/SemanticFixture.java @@ -0,0 +1,78 @@ +/* + * 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.model; + +import java.io.File; +import java.util.List; +import org.sonar.java.test.TestResourceScope; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.SymbolMetadata; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.VariableTree; + +/** Backend-aware source fixtures expose Sonar declarations and retain the parser through lazy queries. */ +final class SemanticFixture implements AutoCloseable { + + private final FrontendParser.ParsedFile parsed; + + private SemanticFixture(FrontendParser.ParsedFile parsed) { + this.parsed = parsed; + } + + static SemanticFixture scoped(String source) { + return scoped(source, JParserTestUtils.DEFAULT_CLASSPATH); + } + + static SemanticFixture scoped(String source, List classpath) { + return TestResourceScope.retain(new SemanticFixture(FrontendParser.parse(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, classpath, "File.java", source))); + } + + static SemanticFixture open(FrontendBackend backend, FrontendConfiguration configuration, String unitName, String source) { + return new SemanticFixture(FrontendParser.parse(backend, configuration, unitName, source)); + } + + JavaTree.CompilationUnitTreeImpl tree() { + return parsed.tree(); + } + + ClassTree declaration(int index) { + return (ClassTree) tree().types().get(index); + } + + MethodTree method(int typeIndex, int memberIndex) { + return (MethodTree) declaration(typeIndex).members().get(memberIndex); + } + + VariableTree variable(int typeIndex, int memberIndex) { + return (VariableTree) declaration(typeIndex).members().get(memberIndex); + } + + Type type(String name) { + return tree().sema.getClassType(name); + } + + List annotations(Symbol symbol) { + return symbol.metadata().annotations(); + } + + @Override + public void close() { + parsed.close(); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/model/SemanticFixtureTest.java b/java-frontend/src/test/java/org/sonar/java/model/SemanticFixtureTest.java new file mode 100644 index 00000000000..1f23503b3c7 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/SemanticFixtureTest.java @@ -0,0 +1,95 @@ +/* + * 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.model; + +import java.io.File; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.sonar.java.test.TestResourceScope; +import org.sonar.plugins.java.api.tree.ClassTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@TestResourceScope.Scoped +class SemanticFixtureTest { + + @Test + void fixtures_expose_declarations_types_metadata_defaults_and_recovery() { + SemanticFixture fixture = SemanticFixture.scoped("@interface A { String value() default \"default\"; } @A class C { Missing missing; int field; void m() {} }"); + assertThat(fixture.method(0, 0).symbol().defaultValue()).isEqualTo("default"); + assertThat(fixture.annotations(fixture.declaration(1).symbol())).hasSize(1); + assertThat(fixture.annotations(fixture.declaration(1).symbol()).get(0).symbol().name()).isEqualTo("A"); + assertThat(fixture.variable(1, 0).symbol().type().isUnknown()).isTrue(); + assertThat(fixture.variable(1, 1).symbol().type()).isSameAs(fixture.type("int")); + assertThat(fixture.method(1, 2).symbol().declaration()).isSameAs(fixture.method(1, 2)); + assertThat(fixture.type("java.lang.String")).isSameAs(fixture.type("java.lang.String")); + } + + @Test + void injected_backend_failure_closes_resources_and_preserves_failure() { + AtomicInteger cleaned = new AtomicInteger(); + IllegalStateException failure = new IllegalStateException("injected backend"); + FrontendBackend backend = configuration -> new FrontendSession() { + { + own(cleaned::incrementAndGet); + } + + @Override + JavaTree.CompilationUnitTreeImpl parse(String unitName, String source) { + throw failure; + } + + @Override + void parseBatch(FrontendBatch inputs, org.sonar.java.ProgressMonitor monitor, + java.util.function.BiConsumer action) { + throw new AssertionError("unexpected batch parse"); + } + }; + assertThatThrownBy(() -> SemanticFixture.open(backend, + FrontendConfiguration.from(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false), "A.java", "class A {}")) + .isSameAs(failure); + assertThat(cleaned).hasValue(1); + } + + @Test + void helper_preserves_string_input_and_existing_file_line_normalization(@TempDir java.nio.file.Path directory) throws Exception { + String source = "class A {\r\n String value = \"😀\";\r\n}\r\n"; + var direct = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); + assertThat(direct.eofToken().range().start().line()).isEqualTo(4); + java.nio.file.Path file = directory.resolve("A.java"); + java.nio.file.Files.writeString(file, source); + var fromFile = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(file.toFile()); + assertThat(fromFile.eofToken().range().start().line()).isEqualTo(3); + assertThat(((ClassTree) direct.types().get(0)).symbol().name()).isEqualTo(((ClassTree) fromFile.types().get(0)).symbol().name()); + } + + @Test + void effective_configuration_is_immutable_and_keeps_classpath_order() { + var classpath = new java.util.ArrayList<>(List.of(new File("first"), new File("second"))); + FrontendConfiguration configuration = FrontendConfiguration.from(new JavaVersionImpl(17, true), classpath, true); + classpath.clear(); + assertThat(configuration.classpath()).containsExactly(new File("first"), new File("second")); + List configuredClasspath = configuration.classpath(); + assertThatThrownBy(configuredClasspath::clear).isInstanceOf(UnsupportedOperationException.class); + assertThat(configuration.sourceVersion()).isEqualTo("17"); + assertThat(configuration.previewEnabled()).isTrue(); + assertThat(configuration.ignoreUnnamedModuleForSplitPackage()).isTrue(); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/model/SemanticIdentityTest.java b/java-frontend/src/test/java/org/sonar/java/model/SemanticIdentityTest.java new file mode 100644 index 00000000000..b9adafcfada --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/SemanticIdentityTest.java @@ -0,0 +1,46 @@ +/* + * 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.model; + +import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.ReturnStatementTree; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestResourceScope.Scoped +class SemanticIdentityTest { + @Test + void generic_invocations_preserve_declaration_and_instantiation_identity() { + var tree = JParserTestUtils.parse("class A { T id(T t) { return t; } String a() { return id(\"\"); } Integer b() { return id(1); } }"); + var type = (ClassTree) tree.types().get(0); + var first = invocation((MethodTree) type.members().get(1)); + var second = invocation((MethodTree) type.members().get(2)); + assertThat(first.methodSymbol()).isSameAs(first.methodSymbol()).isNotEqualTo(second.methodSymbol()); + assertThat(first.methodSymbol().declaration()).isSameAs(second.methodSymbol().declaration()); + assertThat(first.methodSymbol().parameterTypes().get(0).fullyQualifiedName()).isEqualTo("java.lang.String"); + assertThat(second.methodSymbol().parameterTypes().get(0).fullyQualifiedName()).isEqualTo("java.lang.Integer"); + assertThat(first.symbolType()).isSameAs(first.symbolType()); + } + + private static MethodInvocationTree invocation(MethodTree method) { + return (MethodInvocationTree) ((ReturnStatementTree) method.block().body().get(0)).expression(); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java b/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java index cf0d6e33345..7c7e42e7b1e 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.when; import static org.sonar.java.model.EcjTestSupport.sema; +@TestResourceScope.Scoped class SymbolsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/SyntacticEquivalenceTest.java b/java-frontend/src/test/java/org/sonar/java/model/SyntacticEquivalenceTest.java index 440839a0cfb..1785760006c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/SyntacticEquivalenceTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/SyntacticEquivalenceTest.java @@ -21,6 +21,7 @@ import java.util.List; import org.assertj.core.api.AbstractBooleanAssert; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; @@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class SyntacticEquivalenceTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/TypeUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/TypeUtilsTest.java index 919a8ee5353..33a6ff6753c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/TypeUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/TypeUtilsTest.java @@ -19,6 +19,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import org.junit.jupiter.api.Test; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.VariableTree; @@ -27,6 +28,7 @@ import static java.lang.reflect.Modifier.isPrivate; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class TypeUtilsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java index 76df3e194d3..e02431ed82b 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java @@ -43,6 +43,7 @@ import org.sonar.java.checks.VisitorThatCanBeSkipped; import org.sonar.java.exceptions.ApiMismatchException; import org.sonar.java.notchecks.VisitorNotInChecksPackage; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.DependencyVersionAware; import org.sonar.plugins.java.api.InputFileScannerContext; @@ -74,6 +75,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +@TestResourceScope.Scoped class VisitorsBridgeTest { @RegisterExtension @@ -83,7 +85,7 @@ class VisitorsBridgeTest { private static final File FILE = new File("src/test/files/model/SimpleClass.java"); private static final InputFile INPUT_FILE = TestUtils.inputFile(FILE); - private static final CompilationUnitTree COMPILATION_UNIT_TREE = JParserTestUtils.parse(FILE); + private final CompilationUnitTree compilationUnitTree = JParserTestUtils.parse(FILE); private static final NullPointerException NPE = new NullPointerException("BimBadaboum"); @@ -91,7 +93,7 @@ class VisitorsBridgeTest { void rethrow_exception_when_hidden_property_set_to_true_with_JavaFileScanner() { VisitorsBridge visitorsBridge = visitorsBridge(new JFS_ThrowingNPEJavaFileScanner(), true); - assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatThrownBy(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .isInstanceOf(AnalysisException.class) .hasMessageContaining("Failing check") .hasCauseInstanceOf(CheckFailureException.class) @@ -106,7 +108,7 @@ void rethrow_exception_when_hidden_property_set_to_true_with_JavaFileScanner() { void swallow_exception_when_hidden_property_set_to_false_with_JavaFileScanner() { assertThatCode(() -> visitorsBridge(new JFS_ThrowingNPEJavaFileScanner(), false) - .visitFile(COMPILATION_UNIT_TREE, false) + .visitFile(compilationUnitTree, false) ) .withFailMessage("Exception should be swallowed when property is not set") .doesNotThrowAnyException(); @@ -120,7 +122,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_JavaFileScanner() void rethrow_exception_when_hidden_property_set_to_true_with_SubscriptionVisitor() { VisitorsBridge visitorsBridge = visitorsBridge(new SV1_ThrowingNPEVisitingClass(), true); - assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatThrownBy(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .isInstanceOf(AnalysisException.class) .hasMessageContaining("Failing check") .hasCauseInstanceOf(CheckFailureException.class) @@ -140,7 +142,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_SubscriptionVisito new SV3_ThrowingNPEVisitingToken(), new SV4_ThrowingNPEVisitingTrivia()), false) - .visitFile(COMPILATION_UNIT_TREE, false); + .visitFile(compilationUnitTree, false); }) .withFailMessage("Exceptions should be swallowed when property is not set") .doesNotThrowAnyException(); @@ -161,7 +163,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_IssuableSubscripti new IV1_ThrowingNPEVisitingClass(), new IV2_ThrowingNPELeavingClass()), false) - .visitFile(COMPILATION_UNIT_TREE, false); + .visitFile(compilationUnitTree, false); }) .withFailMessage("Exceptions should be swallowed when property is not set") .doesNotThrowAnyException(); @@ -175,7 +177,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_IssuableSubscripti void clear_state_when_leave_file_does_not_delegate() { StatefulVisitorWithoutLeaveFileDelegation visitor = new StatefulVisitorWithoutLeaveFileDelegation(false); - visitorsBridge(visitor, false).visitFile(COMPILATION_UNIT_TREE, false); + visitorsBridge(visitor, false).visitFile(compilationUnitTree, false); assertThat(visitor.state).isFalse(); assertThat(visitor.leaveCalled).isTrue(); @@ -188,7 +190,7 @@ void clear_state_when_visit_fails_before_leave_file() { VisitorsBridge visitorsBridge = visitorsBridge(visitor, true); - assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatThrownBy(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .isInstanceOf(AnalysisException.class); assertThat(visitor.state).isFalse(); @@ -206,11 +208,11 @@ void clear_all_visitors_state_when_cleanup_fails(boolean failOnException) { VisitorsBridge visitorsBridge = visitorsBridge(List.of(firstVisitor, secondVisitor, lastVisitor), failOnException); if (failOnException) { - assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatThrownBy(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .isInstanceOf(AnalysisException.class) .hasRootCause(firstFailure); } else { - assertThatCode(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)).doesNotThrowAnyException(); + assertThatCode(() -> visitorsBridge.visitFile(compilationUnitTree, false)).doesNotThrowAnyException(); } assertThat(lastVisitor.state).isFalse(); @@ -228,7 +230,7 @@ void cleanup_failure_does_not_mask_visit_failure() { ThrowingCleanupVisitor cleanupVisitor = new ThrowingCleanupVisitor(new IllegalStateException("Cleanup failure")); VisitorsBridge visitorsBridge = visitorsBridge(List.of(failingVisitor, cleanupVisitor), true); - assertThatThrownBy(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatThrownBy(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .isInstanceOf(AnalysisException.class) .hasRootCause(NPE); @@ -243,7 +245,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_all_kinds_of_visis new SV1_ThrowingNPEVisitingClass(), new IV1_ThrowingNPEVisitingClass()), false) - .visitFile(COMPILATION_UNIT_TREE, false); + .visitFile(compilationUnitTree, false); }) .withFailMessage("Exceptions should be swallowed when property is not set") .doesNotThrowAnyException(); @@ -258,7 +260,7 @@ void swallow_exception_when_hidden_property_set_to_false_with_all_kinds_of_visis @Test void no_log_when_filter_execute_fine() { VisitorsBridge visitorsBridge = visitorsBridge(Arrays.asList(), true); - assertThatCode(() -> visitorsBridge.visitFile(COMPILATION_UNIT_TREE, false)) + assertThatCode(() -> visitorsBridge.visitFile(compilationUnitTree, false)) .doesNotThrowAnyException(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/ClassTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/ClassTreeImplTest.java index 4e811aefdaf..4a8660d3949 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/ClassTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/ClassTreeImplTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.JavaTree; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -32,6 +33,7 @@ import static org.sonar.java.model.EcjTestSupport.clearType; import static org.sonar.java.model.assertions.TreeAssert.assertThat; +@TestResourceScope.Scoped class ClassTreeImplTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/ExportsDirectiveTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/ExportsDirectiveTreeImplTest.java index 8d7214bfc1b..3ffd096b673 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/ExportsDirectiveTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/ExportsDirectiveTreeImplTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExportsDirectiveTree; @@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ExportsDirectiveTreeImplTest { private static ExportsDirectiveTree exportsDirective(String exportsDirective) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java index 6ee04398295..c754dd30a01 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java @@ -19,6 +19,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.cfg.ControlFlowGraph; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; @@ -31,6 +32,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +@TestResourceScope.Scoped class MethodTreeImplTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/ModuleDeclarationTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/ModuleDeclarationTreeImplTest.java index 4cd967d2750..2039df27156 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/ModuleDeclarationTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/ModuleDeclarationTreeImplTest.java @@ -19,6 +19,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.AnnotationTree; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ModuleDeclarationTreeImplTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/OpensDirectiveTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/OpensDirectiveTreeImplTest.java index 9db0455d167..ba3b6fe39f3 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/OpensDirectiveTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/OpensDirectiveTreeImplTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExportsDirectiveTree; @@ -34,6 +35,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class OpensDirectiveTreeImplTest { private static OpensDirectiveTree moduleDirective(String exportsDirective) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/ProvidesDirectiveTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/ProvidesDirectiveTreeImplTest.java index 42270e808be..10ceb2a3e1b 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/ProvidesDirectiveTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/ProvidesDirectiveTreeImplTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExportsDirectiveTree; @@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class ProvidesDirectiveTreeImplTest { private static ProvidesDirectiveTree providesDirective(String exportsDirective) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/RequiresDirectiveTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/RequiresDirectiveTreeImplTest.java index 3a28b9419d0..0fdb0c46685 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/RequiresDirectiveTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/RequiresDirectiveTreeImplTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.ModifiersUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.IdentifierTree; @@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class RequiresDirectiveTreeImplTest { private static RequiresDirectiveTree requireDirective(String requiresDirective) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/UsesDirectiveTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/UsesDirectiveTreeImplTest.java index ba2d4a587e9..9fb0abd2565 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/UsesDirectiveTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/UsesDirectiveTreeImplTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.IdentifierTree; @@ -30,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class UsesDirectiveTreeImplTest { private static UsesDirectiveTree moduleDirective(String exportsDirective) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/expression/AssessableExpressionTreeTest.java b/java-frontend/src/test/java/org/sonar/java/model/expression/AssessableExpressionTreeTest.java index 96bf27c54f4..2650c813cfc 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/expression/AssessableExpressionTreeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/expression/AssessableExpressionTreeTest.java @@ -22,6 +22,7 @@ import java.util.function.Function; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; @@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class AssessableExpressionTreeTest { private final ClassTree classTree = parse(); diff --git a/java-frontend/src/test/java/org/sonar/java/model/expression/InstanceOfTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/expression/InstanceOfTreeImplTest.java index ae022081881..353231813b2 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/expression/InstanceOfTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/expression/InstanceOfTreeImplTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.ClassTree; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.model.assertions.TreeAssert.assertThat; +@TestResourceScope.Scoped class InstanceOfTreeImplTest { private static final String CLASS_WITH_INSTANCE_OF = """ diff --git a/java-frontend/src/test/java/org/sonar/java/model/expression/LambdaExpressionTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/expression/LambdaExpressionTreeImplTest.java index 34cd80a1ce0..0f521f53131 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/expression/LambdaExpressionTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/expression/LambdaExpressionTreeImplTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.cfg.ControlFlowGraph; import org.sonar.plugins.java.api.cfg.ControlFlowGraph.Block; import org.sonar.plugins.java.api.semantic.Symbol; @@ -38,6 +39,7 @@ import static org.sonar.java.model.assertions.SymbolAssert.assertThat; import static org.sonar.java.model.assertions.TypeAssert.assertThat; +@TestResourceScope.Scoped class LambdaExpressionTreeImplTest { private static final String CLASS_WITH_METHOD_WITH_LAMBDA = """ diff --git a/java-frontend/src/test/java/org/sonar/java/model/expression/MethodInvocationTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/expression/MethodInvocationTreeImplTest.java index bf73dc9c131..c2654634dc5 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/expression/MethodInvocationTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/expression/MethodInvocationTreeImplTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class MethodInvocationTreeImplTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/pattern/AbstractPatternTreeTest.java b/java-frontend/src/test/java/org/sonar/java/model/pattern/AbstractPatternTreeTest.java index 55927df29f4..9fe50733bdd 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/pattern/AbstractPatternTreeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/pattern/AbstractPatternTreeTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.JavaTree; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.CaseGroupTree; @@ -52,6 +53,7 @@ import static org.sonar.java.model.assertions.TreeAssert.assertThat; import static org.sonar.java.model.assertions.TypeAssert.assertThat; +@TestResourceScope.Scoped class AbstractPatternTreeTest { private static final String BASE_SOURCE_CODE = """ diff --git a/java-frontend/src/test/java/org/sonar/java/model/pattern/RecordPatternTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/pattern/RecordPatternTreeImplTest.java index fe00ce5c424..98245bf2d14 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/pattern/RecordPatternTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/pattern/RecordPatternTreeImplTest.java @@ -19,12 +19,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.location.InternalPosition; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.SwitchStatementTree; import static org.junit.jupiter.api.Assertions.assertEquals; +@TestResourceScope.Scoped class RecordPatternTreeImplTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/springcontext/BeanDefinitionGathererTest.java b/java-frontend/src/test/java/org/sonar/java/model/springcontext/BeanDefinitionGathererTest.java index 950640e21c5..df5227fad7c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/springcontext/BeanDefinitionGathererTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/springcontext/BeanDefinitionGathererTest.java @@ -34,6 +34,7 @@ import org.sonar.api.batch.sensor.cache.WriteCache; import org.sonar.java.TestUtils; import org.sonar.java.telemetry.NoOpTelemetry; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.InputFileScannerContext; import org.sonar.plugins.java.api.caching.CacheContext; @@ -47,6 +48,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class BeanDefinitionGathererTest extends SpringContextGathererTest { @BeforeEach diff --git a/java-frontend/src/test/java/org/sonar/java/model/springcontext/ComponentScanPackageGathererTest.java b/java-frontend/src/test/java/org/sonar/java/model/springcontext/ComponentScanPackageGathererTest.java index bcc6db3ce2d..d6f010ac204 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/springcontext/ComponentScanPackageGathererTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/springcontext/ComponentScanPackageGathererTest.java @@ -27,6 +27,7 @@ import org.sonar.api.batch.sensor.cache.WriteCache; import org.sonar.java.TestUtils; import org.sonar.java.telemetry.NoOpTelemetry; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.InputFileScannerContext; import org.sonar.plugins.java.api.caching.CacheContext; import org.sonar.plugins.java.api.caching.JavaReadCache; @@ -39,6 +40,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class ComponentScanPackageGathererTest extends SpringContextGathererTest { private static final String MODULE_KEY = ""; diff --git a/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextGathererTest.java b/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextGathererTest.java index 8c8e4258c4e..d955766766c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextGathererTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextGathererTest.java @@ -25,6 +25,7 @@ import org.sonar.java.TestUtils; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.VisitorsBridge; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.caching.CacheContext; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped abstract class SpringContextGathererTest { protected SpringContextModelGatherer gatherer; diff --git a/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextModelGathererTest.java b/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextModelGathererTest.java index 5c0edb93347..bdc48567f9a 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextModelGathererTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/springcontext/SpringContextModelGathererTest.java @@ -34,6 +34,7 @@ import org.sonar.java.telemetry.DefaultTelemetry; import org.sonar.java.telemetry.NoOpTelemetry; import org.sonar.java.telemetry.Telemetry; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.InputFileScannerContext; import org.sonar.plugins.java.api.JavaCheck; @@ -43,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +@TestResourceScope.Scoped class SpringContextModelGathererTest { private static final String GATHERING_TIME_KEY = "java.spring.context_model_gathering_time_ms"; diff --git a/java-frontend/src/test/java/org/sonar/java/regex/JavaAnalyzerRegexSourceTest.java b/java-frontend/src/test/java/org/sonar/java/regex/JavaAnalyzerRegexSourceTest.java index 0c1368a8218..31a27d1d20b 100644 --- a/java-frontend/src/test/java/org/sonar/java/regex/JavaAnalyzerRegexSourceTest.java +++ b/java-frontend/src/test/java/org/sonar/java/regex/JavaAnalyzerRegexSourceTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.reporting.AnalyzerMessage; import org.sonar.java.reporting.AnalyzerMessage.TextSpan; +import org.sonar.java.test.TestResourceScope; import org.sonarsource.analyzer.commons.regex.RegexParseResult; import org.sonarsource.analyzer.commons.regex.RegexParser; import org.sonarsource.analyzer.commons.regex.RegexSource; @@ -38,6 +39,7 @@ import static org.sonar.java.regex.RegexParserTestUtils.assertKind; import static org.sonar.java.regex.RegexParserTestUtils.assertSuccessfulParse; +@TestResourceScope.Scoped class JavaAnalyzerRegexSourceTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/regex/RegexCacheTest.java b/java-frontend/src/test/java/org/sonar/java/regex/RegexCacheTest.java index dc39d9f6903..b8d0b6f2909 100644 --- a/java-frontend/src/test/java/org/sonar/java/regex/RegexCacheTest.java +++ b/java-frontend/src/test/java/org/sonar/java/regex/RegexCacheTest.java @@ -19,6 +19,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.LiteralTree; @@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class RegexCacheTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/regex/RegexCheckTest.java b/java-frontend/src/test/java/org/sonar/java/regex/RegexCheckTest.java index f0918a4d005..9edf689b192 100644 --- a/java-frontend/src/test/java/org/sonar/java/regex/RegexCheckTest.java +++ b/java-frontend/src/test/java/org/sonar/java/regex/RegexCheckTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.reporting.AnalyzerMessage.TextSpan; +import org.sonar.java.test.TestResourceScope; import org.sonarsource.analyzer.commons.regex.ast.CharacterTree; import org.sonarsource.analyzer.commons.regex.ast.RegexSyntaxElement; import org.sonarsource.analyzer.commons.regex.ast.RegexTree; @@ -29,6 +30,7 @@ import static org.sonar.java.regex.RegexParserTestUtils.assertKind; import static org.sonar.java.regex.RegexParserTestUtils.assertSuccessfulParse; +@TestResourceScope.Scoped class RegexCheckTest implements RegexCheck { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/reporting/AnalyzerMessageTest.java b/java-frontend/src/test/java/org/sonar/java/reporting/AnalyzerMessageTest.java index 699c66b8897..ac19fe87482 100644 --- a/java-frontend/src/test/java/org/sonar/java/reporting/AnalyzerMessageTest.java +++ b/java-frontend/src/test/java/org/sonar/java/reporting/AnalyzerMessageTest.java @@ -21,6 +21,7 @@ import org.sonar.java.TestUtils; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.reporting.AnalyzerMessage.TextSpan; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; +@TestResourceScope.Scoped class AnalyzerMessageTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/reporting/InternalJavaIssueBuilderTest.java b/java-frontend/src/test/java/org/sonar/java/reporting/InternalJavaIssueBuilderTest.java index 9afb8aea25a..78cbefe16a1 100644 --- a/java-frontend/src/test/java/org/sonar/java/reporting/InternalJavaIssueBuilderTest.java +++ b/java-frontend/src/test/java/org/sonar/java/reporting/InternalJavaIssueBuilderTest.java @@ -52,6 +52,7 @@ import org.sonar.java.SonarComponents; import org.sonar.java.TestUtils; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaFileScannerContext; @@ -75,6 +76,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +@TestResourceScope.Scoped class InternalJavaIssueBuilderTest { @RegisterExtension diff --git a/java-frontend/src/test/java/org/sonar/java/reporting/JavaTextEditTest.java b/java-frontend/src/test/java/org/sonar/java/reporting/JavaTextEditTest.java index 8fb438ab222..244d3c82899 100644 --- a/java-frontend/src/test/java/org/sonar/java/reporting/JavaTextEditTest.java +++ b/java-frontend/src/test/java/org/sonar/java/reporting/JavaTextEditTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.expression.LiteralTreeImpl; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.LiteralTree; @@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@TestResourceScope.Scoped class JavaTextEditTest { private static final File JAVA_FILE = new File("src/test/files/api/JavaFileInternalJavaIssueBuilderTest.java"); private static ClassTree classTree; diff --git a/java-frontend/src/test/java/org/sonar/java/testing/JavaFileScannerContextForTestsTest.java b/java-frontend/src/test/java/org/sonar/java/testing/JavaFileScannerContextForTestsTest.java index 40607240d27..fbb1987ec7f 100644 --- a/java-frontend/src/test/java/org/sonar/java/testing/JavaFileScannerContextForTestsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/testing/JavaFileScannerContextForTestsTest.java @@ -38,6 +38,7 @@ import org.sonar.java.reporting.AnalyzerMessage; import org.sonar.java.reporting.FluentReporting; import org.sonar.java.reporting.JavaQuickFix; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.ClassTree; @@ -45,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +@TestResourceScope.Scoped class JavaFileScannerContextForTestsTest { private static final JavaCheck CHECK = new DummyRule(); diff --git a/java-frontend/src/test/java/org/sonar/java/testing/VisitorsBridgeForTestsTest.java b/java-frontend/src/test/java/org/sonar/java/testing/VisitorsBridgeForTestsTest.java index 0ed0e0a2987..29f91a36fb7 100644 --- a/java-frontend/src/test/java/org/sonar/java/testing/VisitorsBridgeForTestsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/testing/VisitorsBridgeForTestsTest.java @@ -30,6 +30,7 @@ import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.JavaVersionImpl; import org.sonar.java.reporting.AnalyzerMessage; +import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +@TestResourceScope.Scoped class VisitorsBridgeForTestsTest { @Test diff --git a/java-frontend/src/test/java/org/sonar/java/utils/PackageUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/utils/PackageUtilsTest.java index 29a30d158ff..70159701cb2 100644 --- a/java-frontend/src/test/java/org/sonar/java/utils/PackageUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/utils/PackageUtilsTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestResourceScope.Scoped class PackageUtilsTest { // ---- packageName(PackageDeclarationTree, String) -------------------------- diff --git a/java-frontend/src/test/java/org/sonar/java/utils/SpringUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/utils/SpringUtilsTest.java index a37248c11d1..2eee64bfb5c 100644 --- a/java-frontend/src/test/java/org/sonar/java/utils/SpringUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/utils/SpringUtilsTest.java @@ -27,6 +27,7 @@ import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.springcontext.InjectionPoint; import org.sonar.java.reporting.AnalyzerMessage; +import org.sonar.java.test.TestResourceScope; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -38,6 +39,7 @@ import static org.sonar.java.model.springcontext.ProfileExpression.profile; import static org.assertj.core.api.Assertions.tuple; +@TestResourceScope.Scoped class SpringUtilsTest { @Test From 2edd634a23573a47309e4e2704f0bea0b86b9bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 3 Nov 2026 08:26:55 +0100 Subject: [PATCH 3/3] Formatting --- .../java/checks/helpers/JParserTestUtils.java | 2 - .../helpers/ReassignmentFinderTest.java | 1 - .../sonar/java/test/TestResourceScope.java | 8 +++- .../java/test/TestResourceScopeTest.java | 38 ++++++++++++++----- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index 2c16a2321fe..132e9597e9a 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -17,13 +17,11 @@ package org.sonar.java.checks.helpers; import com.google.common.io.Files; - import java.io.File; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; - import org.sonar.java.model.FrontendParser; import org.sonar.java.model.JParserConfig; import org.sonar.java.test.TestResourceScope; diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java index 9f0205b51ff..8af0e9cf332 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ReassignmentFinderTest.java @@ -18,7 +18,6 @@ import java.lang.reflect.Constructor; import java.util.List; - import org.junit.jupiter.api.Test; import org.sonar.java.test.TestResourceScope; import org.sonar.plugins.java.api.semantic.Symbol; diff --git a/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java b/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java index 2c8134d7163..4dd59028939 100644 --- a/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java +++ b/java-checks-test-sources/test-classpath-reader/src/main/java/org/sonar/java/test/TestResourceScope.java @@ -30,7 +30,9 @@ 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. */ +/** + * 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> SCOPES = ThreadLocal.withInitial(ArrayDeque::new); @@ -85,7 +87,9 @@ public static final class Scope implements AutoCloseable { private Scope() { } - /** Attempts every fixture even after errors, then rethrows the first failure with later failures suppressed. */ + /** + * Attempts every fixture even after errors, then rethrows the first failure with later failures suppressed. + */ @Override public void close() throws Exception { if (closed) { diff --git a/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java b/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java index fa0f4035063..fca7d8a2f4a 100644 --- a/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java +++ b/java-checks-test-sources/test-classpath-reader/src/test/java/org/sonar/java/test/TestResourceScopeTest.java @@ -46,8 +46,14 @@ void scope_closes_every_resource_in_reverse_order_even_when_cleanup_fails() thro 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; }); + 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"); @@ -59,9 +65,13 @@ void scope_cleanup_attempts_every_resource_and_suppresses_errors() throws Except List calls = new ArrayList<>(); AssertionError failure = new AssertionError("cleanup"); TestResourceScope.retain(() -> calls.add("last")); - TestResourceScope.retain(() -> { throw failure; }); + TestResourceScope.retain(() -> { + throw failure; + }); Exception first = new Exception("first"); - TestResourceScope.retain(() -> { throw first; }); + TestResourceScope.retain(() -> { + throw first; + }); assertThatThrownBy(scope::close).isSameAs(first).hasSuppressedException(failure); scope.close(); @@ -79,9 +89,15 @@ void scope_cleanup_rethrows_first_error_after_attempting_every_resource() throws 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; }); + TestResourceScope.retain(() -> { + throw second; + }); + TestResourceScope.retain(() -> { + throw first; + }); + TestResourceScope.retain(() -> { + throw first; + }); assertThatThrownBy(scope::close).isSameAs(first).hasSuppressedException(second); scope.close(); @@ -99,7 +115,9 @@ void missing_scope_fails_loudly_and_closes_the_unretained_fixture() { @Test void missing_scope_preserves_cleanup_exceptions_as_suppressed() { Exception failure = new Exception("cleanup failed"); - assertThatThrownBy(() -> TestResourceScope.retain(() -> { throw failure; })) + assertThatThrownBy(() -> TestResourceScope.retain(() -> { + throw failure; + })) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("@TestResourceScope.Scoped") .hasSuppressedException(failure); @@ -108,7 +126,9 @@ void missing_scope_preserves_cleanup_exceptions_as_suppressed() { @Test void missing_scope_propagates_cleanup_errors() { AssertionError failure = new AssertionError("cleanup failed"); - assertThatThrownBy(() -> TestResourceScope.retain(() -> { throw failure; })) + assertThatThrownBy(() -> TestResourceScope.retain(() -> { + throw failure; + })) .isSameAs(failure); } }