From 46a48d0a052df96737a853c4f6f1d2137e101e2d Mon Sep 17 00:00:00 2001 From: ndr_brt Date: Tue, 19 Mar 2024 09:36:46 +0100 Subject: [PATCH] feat: autodoc error on misplaced @Setting annotations (#228) * feat: print warning on misplaced @Setting annotations * fail when @setting is defined in a non-extension class --- .../core/processor/EdcModuleProcessor.java | 30 +- .../introspection/ModuleIntrospector.java | 41 ++- .../EdcModuleProcessorExtensionTest.java | 138 -------- .../processor/EdcModuleProcessorSpiTest.java | 60 ---- .../processor/EdcModuleProcessorTest.java | 322 +++++++++++++----- .../autodoc/core/processor/TestFunctions.java | 47 --- .../test/NotExtensionWithSetting.java | 22 ++ 7 files changed, 303 insertions(+), 357 deletions(-) delete mode 100644 plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorExtensionTest.java delete mode 100644 plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorSpiTest.java delete mode 100644 plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/TestFunctions.java create mode 100644 plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/test/NotExtensionWithSetting.java diff --git a/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessor.java b/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessor.java index e1eeb64f..0a4542f0 100644 --- a/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessor.java +++ b/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessor.java @@ -38,6 +38,7 @@ import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.StandardLocation; @@ -52,8 +53,8 @@ * {@link #EDC_OUTPUTDIR_OVERRIDE} as a processor parameter. */ @SupportedAnnotationTypes({ - "org.eclipse.edc.runtime.metamodel.annotation.EdcSetting", - "org.eclipse.edc.runtime.metamodel.annotation.EdcSettingContext", + "org.eclipse.edc.runtime.metamodel.annotation.Setting", + "org.eclipse.edc.runtime.metamodel.annotation.SettingContext", "org.eclipse.edc.runtime.metamodel.annotation.Extension", "org.eclipse.edc.runtime.metamodel.annotation.Spi", "org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint", @@ -72,7 +73,6 @@ public class EdcModuleProcessor extends AbstractProcessor { private final ObjectMapper mapper = new ObjectMapper(); private ModuleIntrospector moduleIntrospector; - private OverviewIntrospector overviewIntrospector; private EdcModule.Builder moduleBuilder; @@ -80,11 +80,12 @@ public class EdcModuleProcessor extends AbstractProcessor { private ExtensionIntrospector extensionIntrospector; private ModuleType moduleType; + private Set extensionElements; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - moduleIntrospector = new ModuleIntrospector(processingEnv.getElementUtils(), processingEnv.getTypeUtils()); + moduleIntrospector = new ModuleIntrospector(processingEnv); //todo: replace this Noop converter with an actual JavadocConverter overviewIntrospector = new OverviewIntrospector(javadoc -> javadoc, processingEnv.getElementUtils()); @@ -93,8 +94,11 @@ public synchronized void init(ProcessingEnvironment processingEnv) { @Override public boolean process(Set annotations, RoundEnvironment environment) { - if (!initializeModuleBuilder(environment)) { - return false; // error, do not continue processing + if (moduleBuilder == null) { + var result = initializeModuleBuilder(environment); + if (!result) { + return false; // error, do not continue processing + } } if (environment.processingOver()) { @@ -105,8 +109,6 @@ public boolean process(Set annotations, RoundEnvironment } if (moduleType == ModuleType.EXTENSION) { - var extensionElements = moduleIntrospector.getExtensionElements(environment); - extensionElements.forEach(element -> { extensionBuilder = EdcServiceExtension.Builder.newInstance().type(moduleType) .name(extensionIntrospector.getExtensionName(element)) @@ -116,6 +118,7 @@ public boolean process(Set annotations, RoundEnvironment .configuration(extensionIntrospector.resolveConfigurationSettings(element)) .overview(overviewIntrospector.generateModuleOverview(moduleType, environment)) .categories(extensionIntrospector.getExtensionCategories(element)); + moduleBuilder.extension(extensionBuilder.build()); }); } else { @@ -129,11 +132,6 @@ public boolean process(Set annotations, RoundEnvironment } private boolean initializeModuleBuilder(RoundEnvironment environment) { - if (moduleBuilder != null) { - // already initialized in a previous round - return true; - } - var id = processingEnv.getOptions().get(ID); if (id == null) { processingEnv.getMessager().printMessage(ERROR, "Value for '" + ID + "' not set on processor configuration. Skipping manifest generation."); @@ -146,7 +144,8 @@ private boolean initializeModuleBuilder(RoundEnvironment environment) { return false; } - moduleType = determineAndValidateModuleType(environment); + extensionElements = moduleIntrospector.getExtensionElements(environment); + moduleType = determineAndValidateModuleType(environment, extensionElements); if (moduleType == ModuleType.INVALID) { // error or not a module, return return false; @@ -158,8 +157,7 @@ private boolean initializeModuleBuilder(RoundEnvironment environment) { } @Nullable - private ModuleType determineAndValidateModuleType(RoundEnvironment environment) { - var extensionElements = moduleIntrospector.getExtensionElements(environment); + private ModuleType determineAndValidateModuleType(RoundEnvironment environment, Set extensionElements) { if (extensionElements.isEmpty()) { // check if it is an SPI var spiElements = environment.getElementsAnnotatedWith(Spi.class); diff --git a/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/introspection/ModuleIntrospector.java b/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/introspection/ModuleIntrospector.java index 005daf2c..56e4ae8c 100644 --- a/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/introspection/ModuleIntrospector.java +++ b/plugins/autodoc/autodoc-processor/src/main/java/org/eclipse/edc/plugins/autodoc/core/processor/introspection/ModuleIntrospector.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; @@ -35,6 +36,7 @@ import javax.lang.model.util.Types; import static java.util.stream.Collectors.toList; +import static javax.tools.Diagnostic.Kind.ERROR; import static org.eclipse.edc.plugins.autodoc.core.processor.compiler.AnnotationFunctions.attributeStringValues; import static org.eclipse.edc.plugins.autodoc.core.processor.compiler.AnnotationFunctions.attributeValue; import static org.eclipse.edc.plugins.autodoc.core.processor.compiler.AnnotationFunctions.mirrorFor; @@ -46,10 +48,12 @@ public class ModuleIntrospector { private static final String SERVICE_EXTENSION_NAME = "org.eclipse.edc.spi.system.ServiceExtension"; private final Elements elementUtils; private final Types typeUtils; + private final ProcessingEnvironment processingEnv; - public ModuleIntrospector(Elements elementUtils, Types typeUtils) { - this.elementUtils = elementUtils; - this.typeUtils = typeUtils; + public ModuleIntrospector(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + this.elementUtils = processingEnv.getElementUtils(); + this.typeUtils = processingEnv.getTypeUtils(); } @@ -89,23 +93,30 @@ public String getModuleName(RoundEnvironment environment) { * @return a set containing the distinct extension symbols. Elements in that set are most likely of type Symbol.ClassSymbol */ public Set getExtensionElements(RoundEnvironment environment) { - var extensionClasses = environment.getElementsAnnotatedWith(Extension.class); - var settingsSymbols = environment.getElementsAnnotatedWith(Setting.class); + var settingsSymbols = environment.getElementsAnnotatedWith(Setting.class).stream() + .peek(setting -> { + var enclosingElement = setting.getEnclosingElement().asType(); + var serviceExtensionType = typeUtils.erasure(elementUtils.getTypeElement(SERVICE_EXTENSION_NAME).asType()); + if (!typeUtils.isAssignable(enclosingElement, serviceExtensionType)) { + var message = "@Setting annotation must be used inside a ServiceExtension implementation, the " + + "ones defined in %s will be excluded from the autodoc manifest".formatted(enclosingElement); + processingEnv.getMessager().printMessage(ERROR, message, setting); + } + }); + var injectSymbols = environment.getElementsAnnotatedWith(Inject.class); var providerSymbols = environment.getElementsAnnotatedWith(Provider.class); - var providesClasses = environment.getElementsAnnotatedWith(Provides.class); - var requiresClasses = environment.getElementsAnnotatedWith(Requires.class); - - var symbols = settingsSymbols.stream(); - symbols = Stream.concat(symbols, injectSymbols.stream()); - symbols = Stream.concat(symbols, providerSymbols.stream()); - var classes = symbols.map(Element::getEnclosingElement) + var classes = Stream.of(settingsSymbols, injectSymbols.stream(), providerSymbols.stream()) + .reduce(Stream::concat) + .orElse(Stream.empty()) + .map(Element::getEnclosingElement) .filter(this::isExtension) .collect(Collectors.toSet()); - classes.addAll(requiresClasses); - classes.addAll(providesClasses); - classes.addAll(extensionClasses); + + classes.addAll(environment.getElementsAnnotatedWith(Requires.class)); + classes.addAll(environment.getElementsAnnotatedWith(Provides.class)); + classes.addAll(environment.getElementsAnnotatedWith(Extension.class)); return classes; } diff --git a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorExtensionTest.java b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorExtensionTest.java deleted file mode 100644 index c9ffaecd..00000000 --- a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorExtensionTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2022 Microsoft Corporation - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.plugins.autodoc.core.processor; - -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.NotAnExtension; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.OptionalService; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.RequiredService; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SampleExtensionWithoutAnnotation; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SecondExtension; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SettingContextExtension; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SomeOtherService; -import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SomeService; -import org.eclipse.edc.runtime.metamodel.domain.ConfigurationSetting; -import org.eclipse.edc.runtime.metamodel.domain.EdcServiceExtension; -import org.eclipse.edc.runtime.metamodel.domain.Service; -import org.eclipse.edc.runtime.metamodel.domain.ServiceReference; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_CLASS_PREFIX_SETTING_KEY; -import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_FIELD_PREFIX_SETTING_KEY; -import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SETTING_DEFAULT_VALUE; -import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SETTING_ID_KEY; -import static org.eclipse.edc.plugins.autodoc.core.processor.TestFunctions.filterManifest; -import static org.eclipse.edc.plugins.autodoc.core.processor.TestFunctions.readManifest; - -class EdcModuleProcessorExtensionTest extends EdcModuleProcessorTest { - @Test - void verifyManifestContainsExtension() { - task.call(); - - var modules = readManifest(filterManifest(tempDir)); - assertThat(modules).hasSize(1); - assertThat(modules.get(0).getExtensions()) - .hasSize(3) - .extracting(EdcServiceExtension::getName) - .containsOnly(SampleExtensionWithoutAnnotation.class.getSimpleName(), - SecondExtension.class.getSimpleName(), SettingContextExtension.class.getSimpleName()) - .doesNotContain(NotAnExtension.class.getName()); //explicitly exclude this - } - - @Test - void verifyManifestContainsCorrectElements() { - task.call(); - - var modules = readManifest(filterManifest(tempDir)); - assertThat(modules).hasSize(1); - var extensions = modules.get(0).getExtensions(); - - assertThat(extensions) - .allSatisfy(e -> assertThat(e.getCategories()).isNotNull().isEmpty()) - .allSatisfy(e -> assertThat(e.getOverview()).isNull()) - .extracting(EdcServiceExtension::getName) - .containsOnly(SampleExtensionWithoutAnnotation.class.getSimpleName(), SecondExtension.class.getSimpleName(), SettingContextExtension.class.getSimpleName()); - - var ext1 = extensions.stream().filter(e -> e.getName().equals(SampleExtensionWithoutAnnotation.class.getSimpleName())) - .findFirst() - .orElseThrow(); - - var providedServices = ext1.getProvides(); - assertThat(providedServices).hasSize(2) - .extracting(Service::getService) - .containsExactlyInAnyOrder(SomeService.class.getName(), SomeOtherService.class.getName()); - - var references = ext1.getReferences(); - assertThat(references.size()).isEqualTo(2); - assertThat(references).contains(new ServiceReference(OptionalService.class.getName(), false)); - assertThat(references).contains(new ServiceReference(RequiredService.class.getName(), true)); - - assertThat(ext1.getConfiguration()).first().isNotNull().satisfies(configuration -> { - assertThat(configuration).isNotNull(); - assertThat(configuration.getKey()).isEqualTo(SampleExtensionWithoutAnnotation.TEST_SETTING); - assertThat(configuration.isRequired()).isTrue(); - assertThat(configuration.getDefaultValue()).isEqualTo(TEST_SETTING_DEFAULT_VALUE); - assertThat(configuration.getDescription()).isNotEmpty(); - }); - - var ext2 = extensions.stream().filter(e -> e.getName().equals(SecondExtension.class.getSimpleName())) - .findFirst() - .orElseThrow(); - - assertThat(ext2.getProvides()).isEmpty(); - - assertThat(ext2.getReferences()) - .hasSize(1) - .containsOnly(new ServiceReference(SomeService.class.getName(), true)); - - assertThat(ext2.getConfiguration()).isEmpty(); - } - - @Test - void verifyManifestContainsCorrectSettingsWithContext() { - task.call(); - - var modules = readManifest(filterManifest(tempDir)); - assertThat(modules).hasSize(1); - var extensions = modules.get(0).getExtensions(); - - var ext1 = extensions.stream().filter(e -> e.getName().equals(SettingContextExtension.class.getSimpleName())) - .findFirst() - .orElseThrow(); - - - assertThat(ext1.getConfiguration()) - .extracting(ConfigurationSetting::getKey) - .contains(TEST_CLASS_PREFIX_SETTING_KEY + TEST_SETTING_ID_KEY, TEST_FIELD_PREFIX_SETTING_KEY + TEST_SETTING_ID_KEY); - - } - - @Override - protected List getCompilationUnits() { - var f = new File("src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/testextensions/"); - try (var files = Files.list(f.toPath())) { - return files.map(Path::toFile).collect(Collectors.toList()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorSpiTest.java b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorSpiTest.java deleted file mode 100644 index ee776e2a..00000000 --- a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorSpiTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 Microsoft Corporation - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.plugins.autodoc.core.processor; - -import org.eclipse.edc.plugins.autodoc.core.processor.testspi.ExtensionService; -import org.eclipse.edc.runtime.metamodel.domain.Service; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SPI_MODULE; -import static org.eclipse.edc.plugins.autodoc.core.processor.TestFunctions.filterManifest; -import static org.eclipse.edc.plugins.autodoc.core.processor.TestFunctions.readManifest; - -public class EdcModuleProcessorSpiTest extends EdcModuleProcessorTest { - @Test - void verifyCorrectManifest() { - task.call(); - - var manifest = readManifest(filterManifest(tempDir)); - - assertThat(manifest).hasSize(1); - - var module = manifest.get(0); - assertThat(module.getName()).isEqualTo(TEST_SPI_MODULE); - assertThat(module.getVersion()).isEqualTo(EDC_VERSION); - assertThat(module.getModulePath()).isEqualTo(EDC_ID); - assertThat(module.getCategories()).hasSize(1).containsOnly("category").isEqualTo(module.getAllCategories()); - assertThat(module.getExtensionPoints()).hasSize(1).containsOnly(new Service(ExtensionService.class.getName())); - assertThat(module.getExtensions()).isEmpty(); - } - - @Override - protected List getCompilationUnits() { - var f = new File("src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/testspi/"); - try (var files = Files.list(f.toPath())) { - return files.map(Path::toFile).collect(Collectors.toList()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorTest.java b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorTest.java index 942e65d2..4488488c 100644 --- a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorTest.java +++ b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/EdcModuleProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Microsoft Corporation + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at @@ -8,14 +8,30 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Microsoft Corporation - initial API and implementation + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation * */ package org.eclipse.edc.plugins.autodoc.core.processor; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.NotAnExtension; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.OptionalService; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.RequiredService; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SampleExtensionWithoutAnnotation; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SecondExtension; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SettingContextExtension; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SomeOtherService; +import org.eclipse.edc.plugins.autodoc.core.processor.testextensions.SomeService; +import org.eclipse.edc.plugins.autodoc.core.processor.testspi.ExtensionService; +import org.eclipse.edc.runtime.metamodel.domain.ConfigurationSetting; +import org.eclipse.edc.runtime.metamodel.domain.EdcModule; +import org.eclipse.edc.runtime.metamodel.domain.EdcServiceExtension; +import org.eclipse.edc.runtime.metamodel.domain.Service; +import org.eclipse.edc.runtime.metamodel.domain.ServiceReference; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; @@ -30,9 +46,11 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; @@ -40,115 +58,257 @@ import javax.tools.ToolProvider; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_CLASS_PREFIX_SETTING_KEY; +import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_FIELD_PREFIX_SETTING_KEY; +import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SETTING_DEFAULT_VALUE; +import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SETTING_ID_KEY; +import static org.eclipse.edc.plugins.autodoc.core.processor.Constants.TEST_SPI_MODULE; -abstract class EdcModuleProcessorTest { +public class EdcModuleProcessorTest { - protected static final String EDC_ID = "test-edc-id"; - protected static final String EDC_VERSION = "test-edc-version"; + private static final String EDC_ID = "test-edc-id"; + private static final String EDC_VERSION = "test-edc-version"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @TempDir private Path tempDir; + private final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - @TempDir - protected Path tempDir; - protected JavaCompiler.CompilationTask task; - private DiagnosticCollector diagnostics; + @Nested + class CompilerArgs { + @ParameterizedTest + @ArgumentsSource(ValidCompilerArgsProvider.class) + void verifyOptionalCompilerArgs(String edcId, String edcVersion, String edcOutputDir) throws IOException { + var task = createTask(edcId, edcVersion, edcOutputDir, getFiles("testextensions")); - @BeforeEach - void setUp() throws IOException { - var compiler = ToolProvider.getSystemJavaCompiler(); - diagnostics = new DiagnosticCollector<>(); - var fileManager = compiler.getStandardFileManager(diagnostics, null, null); - fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(tempDir.toFile())); - fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(tempDir.toFile())); + var errorMsg = diagnostics.getDiagnostics().stream().map(Object::toString).collect(Collectors.joining(", ")); + assertThat(task.call()).withFailMessage(errorMsg).isTrue(); + } - var compilationUnits = fileManager.getJavaFileObjects(getCompilationUnits().toArray(File[]::new)); + @ParameterizedTest + @ArgumentsSource(InvalidCompilerArgsProvider.class) + void verifyRequiredCompilerArgs(String edcId, String edcVersion, String edcOutputDir) throws IOException { + var task = createTask(edcId, edcVersion, edcOutputDir, getFiles("testextensions")); - var options = List.of("-Aedc.id=" + EDC_ID, "-Aedc.version=" + EDC_VERSION); - task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); + assertThat(task.call()).isFalse(); + } - task.setProcessors(Collections.singletonList(new EdcModuleProcessor())); - } + @Test + void shouldCreateManifestFile_tempDir() throws IOException { + var task = createTask("testextensions"); - @ParameterizedTest - @ArgumentsSource(ValidCompilerArgsProvider.class) - void verifyOptionalCompilerArgs(String edcId, String edcVersion, String edcOutputdir) throws IOException { - var compiler = ToolProvider.getSystemJavaCompiler(); - diagnostics = new DiagnosticCollector<>(); - var fileManager = compiler.getStandardFileManager(diagnostics, null, null); - fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(tempDir.toFile())); - fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(tempDir.toFile())); + var result = task.call(); + assertThat(result).isTrue(); + assertThat(Files.list(tempDir)) + .withFailMessage("Should contain edc.json") + .anyMatch(p -> p.getFileName().endsWith("edc.json")); + } - var compilationUnits = fileManager.getJavaFileObjects(getCompilationUnits().toArray(File[]::new)); + @Test + void shouldOverrideOutputFolder() throws IOException { + var newTempDir = Files.createTempDirectory("test"); + var task = createTask("-Aedc.version=1.2.3", "-Aedc.id=someid", "-Aedc.outputDir=" + newTempDir, getFiles("testextensions")); - var options = Stream.of(edcId, edcVersion, edcOutputdir).filter(Objects::nonNull).collect(Collectors.toList()); - task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); + var result = task.call(); - task.setProcessors(Collections.singletonList(new EdcModuleProcessor())); - var errorMsg = diagnostics.getDiagnostics().stream().map(Object::toString).collect(Collectors.joining(", ")); - assertThat(task.call()).withFailMessage(errorMsg).isTrue(); + assertThat(result).isTrue(); + assertThat(Files.list(newTempDir)).anyMatch(p -> p.getFileName().endsWith("edc.json")); + } + + private static class ValidCompilerArgsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", "-Aedc.outputDir=build/some/dir"), + Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", null), + Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", "-Aedc.outputDir=") + ); + } + } + + private static class InvalidCompilerArgsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=", "-Aedc.outputDir=some/dir"), + Arguments.of("-Aedc.version=1.2.3", null, "-Aedc.outputDir=some/dir"), + Arguments.of("-Aedc.version=", "-Aedc.id=someid", "-Aedc.outputDir=some/dir"), + Arguments.of(null, "-Aedc.id=someid", "-Aedc.outputDir=some/dir") + ); + } + + } } - @ParameterizedTest - @ArgumentsSource(InvalidCompilerArgsProvider.class) - void verifyRequiredCompilerArgs(String edcId, String edcVersion, String edcOutputdir) throws IOException { - var compiler = ToolProvider.getSystemJavaCompiler(); - diagnostics = new DiagnosticCollector<>(); - var fileManager = compiler.getStandardFileManager(diagnostics, null, null); - fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(tempDir.toFile())); - fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(tempDir.toFile())); + @Nested + class Spi { + @Test + void verifyCorrectManifest() { + var task = createTask("testspi"); + task.call(); - var compilationUnits = fileManager.getJavaFileObjects(getCompilationUnits().toArray(File[]::new)); + var manifest = readManifest(); - var options = Stream.of(edcId, edcVersion, edcOutputdir).filter(Objects::nonNull).collect(Collectors.toList()); - task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); + assertThat(manifest).hasSize(1); - task.setProcessors(Collections.singletonList(new EdcModuleProcessor())); - assertThat(task.call()).isFalse(); + var module = manifest.get(0); + assertThat(module.getName()).isEqualTo(TEST_SPI_MODULE); + assertThat(module.getVersion()).isEqualTo(EDC_VERSION); + assertThat(module.getModulePath()).isEqualTo(EDC_ID); + assertThat(module.getCategories()).hasSize(1).containsOnly("category").isEqualTo(module.getAllCategories()); + assertThat(module.getExtensionPoints()).hasSize(1).containsOnly(new Service(ExtensionService.class.getName())); + assertThat(module.getExtensions()).isEmpty(); + } } - @AfterEach - void tearDown() { + @Nested + class Extension { + @Test + void verifyManifestContainsExtension() { + var task = createTask("testextensions"); + + task.call(); + + var modules = readManifest(); + + assertThat(modules).hasSize(1); + assertThat(modules.get(0).getExtensions()) + .hasSize(3) + .extracting(EdcServiceExtension::getName) + .containsOnly(SampleExtensionWithoutAnnotation.class.getSimpleName(), + SecondExtension.class.getSimpleName(), SettingContextExtension.class.getSimpleName()) + .doesNotContain(NotAnExtension.class.getName()); //explicitly exclude this + } + + @Test + void verifyManifestContainsCorrectElements() { + var task = createTask("testextensions"); + + task.call(); + + var modules = readManifest(); + assertThat(modules).hasSize(1); + var extensions = modules.get(0).getExtensions(); + + assertThat(extensions) + .allSatisfy(e -> assertThat(e.getCategories()).isNotNull().isEmpty()) + .allSatisfy(e -> assertThat(e.getOverview()).isNull()) + .extracting(EdcServiceExtension::getName) + .containsOnly(SampleExtensionWithoutAnnotation.class.getSimpleName(), SecondExtension.class.getSimpleName(), SettingContextExtension.class.getSimpleName()); + + var ext1 = extensions.stream().filter(e -> e.getName().equals(SampleExtensionWithoutAnnotation.class.getSimpleName())) + .findFirst() + .orElseThrow(); + + var providedServices = ext1.getProvides(); + assertThat(providedServices).hasSize(2) + .extracting(Service::getService) + .containsExactlyInAnyOrder(SomeService.class.getName(), SomeOtherService.class.getName()); + + var references = ext1.getReferences(); + assertThat(references.size()).isEqualTo(2); + assertThat(references).contains(new ServiceReference(OptionalService.class.getName(), false)); + assertThat(references).contains(new ServiceReference(RequiredService.class.getName(), true)); + + assertThat(ext1.getConfiguration()).first().isNotNull().satisfies(configuration -> { + assertThat(configuration).isNotNull(); + assertThat(configuration.getKey()).isEqualTo(SampleExtensionWithoutAnnotation.TEST_SETTING); + assertThat(configuration.isRequired()).isTrue(); + assertThat(configuration.getDefaultValue()).isEqualTo(TEST_SETTING_DEFAULT_VALUE); + assertThat(configuration.getDescription()).isNotEmpty(); + }); + + var ext2 = extensions.stream().filter(e -> e.getName().equals(SecondExtension.class.getSimpleName())) + .findFirst() + .orElseThrow(); + + assertThat(ext2.getProvides()).isEmpty(); + + assertThat(ext2.getReferences()) + .hasSize(1) + .containsOnly(new ServiceReference(SomeService.class.getName(), true)); + + assertThat(ext2.getConfiguration()).isEmpty(); + } + + @Test + void verifyManifestContainsCorrectSettingsWithContext() { + var task = createTask("testextensions"); + + task.call(); + + var modules = readManifest(); + assertThat(modules).hasSize(1); + var extensions = modules.get(0).getExtensions(); + + var ext1 = extensions.stream().filter(e -> e.getName().equals(SettingContextExtension.class.getSimpleName())) + .findFirst() + .orElseThrow(); + + assertThat(ext1.getConfiguration()) + .extracting(ConfigurationSetting::getKey) + .contains(TEST_CLASS_PREFIX_SETTING_KEY + TEST_SETTING_ID_KEY, TEST_FIELD_PREFIX_SETTING_KEY + TEST_SETTING_ID_KEY); + } + } @Test - void verifySuccessfulCompilation() { - var success = task.call(); - var errorMsg = diagnostics.getDiagnostics().stream().map(Object::toString).collect(Collectors.joining(", ")); - assertThat(success).withFailMessage(errorMsg).isTrue(); + void shouldFail_whenSettingIsDefinedInClassNotExtension() { + var task = createTask("test/NotExtensionWithSetting.java"); + + assertThat(task.call()).isFalse(); + assertThat(diagnostics.getDiagnostics()).anySatisfy(diagnostic -> { + assertThat(diagnostic.getKind()).isEqualTo(Diagnostic.Kind.ERROR); + assertThat(diagnostic.getMessage(Locale.getDefault())).contains("@Setting"); + }); } - @Test - void verifyManifestExists() throws IOException { - task.call(); - assertThat(Files.list(tempDir)) - .withFailMessage("Should contain edc.json") - .anyMatch(p -> p.getFileName().endsWith("edc.json")); + private JavaCompiler.@NotNull CompilationTask createTask(String classPath) { + try { + var classes = getFiles(classPath); + return createTask("-Aedc.id=" + EDC_ID, "-Aedc.version=" + EDC_VERSION, null, classes); + } catch (IOException e) { + fail("Cannot create task", e); + throw new AssertionError(e); + } } + private JavaCompiler.CompilationTask createTask(String edcId, String edcVersion, String edcOutputDir, File[] classes) throws IOException { + var compiler = ToolProvider.getSystemJavaCompiler(); - protected abstract List getCompilationUnits(); + var fileManager = compiler.getStandardFileManager(diagnostics, null, null); + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(tempDir.toFile())); + fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(tempDir.toFile())); + + var compilationUnits = fileManager.getJavaFileObjects(classes); + + var options = Stream.of(edcId, edcVersion, edcOutputDir).filter(Objects::nonNull).toList(); + var task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); + task.setProcessors(Collections.singletonList(new EdcModuleProcessor())); + return task; + } - private static class ValidCompilerArgsProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", "-Aedc.outputDir=build/some/dir"), - Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", null), - Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=someid", "-Aedc.outputDir=") - ); + @NotNull + private File[] getFiles(String classPath) throws IOException { + var fullPath = "src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/" + classPath; + var file = new File(fullPath); + File[] classes; + if (file.isDirectory()) { + classes = Files.list(file.toPath()).map(Path::toFile).toArray(File[]::new); + } else { + classes = new File[] { file }; } + return classes; } - private static class InvalidCompilerArgsProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of("-Aedc.version=1.2.3", "-Aedc.id=", "-Aedc.outputDir=some/dir"), - Arguments.of("-Aedc.version=1.2.3", null, "-Aedc.outputDir=some/dir"), - Arguments.of("-Aedc.version=", "-Aedc.id=someid", "-Aedc.outputDir=some/dir"), - Arguments.of(null, "-Aedc.id=someid", "-Aedc.outputDir=some/dir") - ); + private List readManifest() { + try (var files = Files.list(tempDir)) { + var url = files.filter(p -> p.endsWith("edc.json")).findFirst().orElseThrow(AssertionError::new).toUri().toURL(); + return OBJECT_MAPPER.readValue(url, new TypeReference<>() { }); + } catch (IOException e) { + throw new AssertionError(e); } } -} \ No newline at end of file +} diff --git a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/TestFunctions.java b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/TestFunctions.java deleted file mode 100644 index 5ac58ba2..00000000 --- a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/TestFunctions.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022 Microsoft Corporation - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.plugins.autodoc.core.processor; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.edc.runtime.metamodel.domain.EdcModule; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -public class TestFunctions { - private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() { - }; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - public static Path filterManifest(Path tempDir) { - try (var files = Files.list(tempDir)) { - return files.filter(p -> p.endsWith("edc.json")).findFirst().orElseThrow(AssertionError::new); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - public static List readManifest(Path manifestFile) { - try (var stream = manifestFile.toUri().toURL().openStream()) { - return OBJECT_MAPPER.readValue(stream, TYPE_REFERENCE); - } catch (IOException e) { - throw new AssertionError("Unexpected error while reading manifest", e); - } - } - -} diff --git a/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/test/NotExtensionWithSetting.java b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/test/NotExtensionWithSetting.java new file mode 100644 index 00000000..e7816d65 --- /dev/null +++ b/plugins/autodoc/autodoc-processor/src/test/java/org/eclipse/edc/plugins/autodoc/core/processor/test/NotExtensionWithSetting.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.plugins.autodoc.core.processor.test; + +import org.eclipse.edc.runtime.metamodel.annotation.Setting; + +public class NotExtensionWithSetting { + @Setting("the setting must stay in a ServiceExtension class") + private static final String UNEXPECTED_SETTING = "any"; +}