From bababb5e599fa0e5ddd74a60aa2945802f3efd27 Mon Sep 17 00:00:00 2001 From: David Mortensen <156092822+lfvdavid@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:43:31 +0100 Subject: [PATCH] 83 add reqstool configyml to reqstool zip file (#87) * feat: Add reqstool prefix to property names * feat: Add reqstool_config.yml to zip artifact * feat: WIP * feat: Fix logic adding files to zip file * feat: Update docs, fix paths set in reqstool_config.yml * feat: Fix test_results in generated reqstool_config.yml * feat: WIP * feat: Update configuration.adoc * feat: WIP * feat: Clarify default phase --- docs/modules/ROOT/pages/configuration.adoc | 163 +++++++++++----- docs/modules/ROOT/pages/usage.adoc | 9 +- .../plugins/maven/RequirementsToolMojo.java | 176 +++++++++++++----- .../maven/RequirementsToolMojoTests.java | 32 ++-- 4 files changed, 276 insertions(+), 104 deletions(-) diff --git a/docs/modules/ROOT/pages/configuration.adoc b/docs/modules/ROOT/pages/configuration.adoc index 1150fb4..a216ca1 100644 --- a/docs/modules/ROOT/pages/configuration.adoc +++ b/docs/modules/ROOT/pages/configuration.adoc @@ -1,17 +1,61 @@ == Configuration -To configure the plugin add a block for the plugin under the in pom.xml +To configure the plugin add a `` block for the plugin under the `` in pom.xml. + +== Complete Configuration Example + +```xml + + + + se.lfv.reqstool + reqstool-maven-plugin + 0.1.0 + + + + ${project.build.directory}/generated-sources/annotations/resources/annotations.yml + + + ${project.build.directory}/generated-test-sources/test-annotations/resources/annotations.yml + + + ${project.build.directory}/reqstool + + ${project.basedir}/reqstool + + + + target/surefire-reports/**/*.xml + target/failsafe-reports/**/*.xml + + + + ${project} + ${log} + + + false + false + false + + + + +``` + +== Configuration Parameters === requirementsAnnotationsFile The `requirementsAnnotationsFile` parameter specifies the path to the requirements annotations file. -Defaults to the value set below. +Defaults to the value set below. ```xml - - ${project.build.directory}/generated-sources/annotations/resources/annotations.yml - + + ${project.build.directory}/generated-sources/annotations/resources/annotations.yml + ``` @@ -19,99 +63,132 @@ Defaults to the value set below. === svcsAnnotationsFile The `svcsAnnotationsFile` parameter specifies the path to the svcs annotations file. -Defaults to the value set below. +Defaults to the value set below. ```xml - - ${project.build.directory}/generated-test-sources/test-annotations/resources/annotations - + + ${project.build.directory}/generated-test-sources/test-annotations/resources/annotations.yml + ``` === outputDirectory -The `outputDirectory` parameter specifies the path to where to put the generated output -Defaults to the value set below. +The `outputDirectory` parameter specifies the path to where to put the generated output. +Defaults to the value set below. ```xml - - ${project.build.directory}/reqstool - + + ${project.build.directory}/reqstool + ``` === datasetPath -The `datasetPath` parameter specifies the path to the dataset -Defaults to the value set below. +The `datasetPath` parameter specifies the path to the dataset. +Defaults to the value set below. ```xml - - ${project.basedir}/reqstool - + + ${project.basedir}/reqstool + ``` -=== failsafeReportsDir +=== project -The `failsafeReportsDir` parameter specifies the path to where the failsafe reports are found. -Defaults to the value set below. +The `project` parameter specifies the name of the maven project. +Defaults to the value set below. ```xml - - ${project.build.directory}/failsafe-reports - + + ${project} + ``` -=== surefireReportsDir +=== testResults -The `surefireReportsDir` parameter specifies the path to where the surefire reports are found. -Defaults to the value set below. +The `testResults` parameter specifies one or more testResult paths. ```xml - - ${project.build.directory}/surefire-reports - + + path/to/test/result.xml + path/to/test/result.xml + ``` -=== project +=== log -The `project` parameter specifies the name of the maven project -Defaults to the value set below. +The `log` parameter specifies the log. +Defaults to the value set below. ```xml - - ${project} - + + ${log} + ``` -=== log +=== skip + +Skip the execution of the plugin. +Defaults to the value set below. + +```xml + + + false + + + +``` + +=== skipAssembleZipArtifact + +Skip zip artifact assembly. +Defaults to the value set below. + +```xml + + + false + + + +``` + +=== skipAttachZipArtifact -The `log` parameter specifies the log -Defaults to the value set below. +Skip zip artifact attachment. +Defaults to the value set below. ```xml - - ${log} - + + false + ``` +Notes: +* All path parameters support both absolute and relative paths +* Maven property placeholders (`${...}`) can be used in all configuration values +* The plugin executes in the `verify` phase by default +* Test result paths support Ant-style pattern matching \ No newline at end of file diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index 3f789b7..bf57e86 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -8,11 +8,11 @@ To use the LFV Reqstool Java Maven Plugin, add the following configuration to yo se.lfv.reqstool reqstool-maven-plugin - 0.0.2 + 0.1.0 - attach-reqstool-zip + assemble-and-attach-zip-artifact @@ -27,3 +27,8 @@ To use the LFV Reqstool Java Maven Plugin, add the following configuration to yo ``` + +The default `` the plugin runs during is `verify`, this is done to ensure all test files are generated before the plugin runs. + + + diff --git a/src/main/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojo.java b/src/main/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojo.java index e603e7a..10b1408 100644 --- a/src/main/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojo.java +++ b/src/main/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojo.java @@ -8,7 +8,21 @@ import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.util.Locale; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -23,6 +37,8 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,30 +47,38 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -@Mojo(name = "assemble-and-attach-zip-artifact", defaultPhase = LifecyclePhase.PACKAGE) +@Mojo(name = "assemble-and-attach-zip-artifact", defaultPhase = LifecyclePhase.VERIFY) public class RequirementsToolMojo extends AbstractMojo { // Constants - public static final String INPUT_DIR_TEST_RESULTS_FAILSAFE = "test_results/failsafe"; - - public static final String INPUT_DIR_TEST_RESULTS_SUREFIRE = "test_results/surefire"; + private static final String[] OUTPUT_ARTIFACT_TEST_RESULTS_PATTERN = { "test_results/**/*.xml" }; public static final String INPUT_FILE_MANUAL_VERIFICATION_RESULTS_YML = "manual_verification_results.yml"; public static final String INPUT_FILE_REQUIREMENTS_YML = "requirements.yml"; - public static final String INPUT_FILESOFTWARE_VERIFICATION_CASES_YML = "software_verification_cases.yml"; + public static final String INPUT_FILE_SOFTWARE_VERIFICATION_CASES_YML = "software_verification_cases.yml"; + + public static final String INPUT_PATH_DATASET = "reqstool"; public static final String OUTPUT_FILE_ANNOTATIONS_YML_FILE = "annotations.yml"; + private static final String OUTPUT_ARTIFACT_CLASSIFIER = "reqstool"; + + public static final String OUTPUT_ARTIFACT_FILE_REQSTOOL_CONFIG_YML = "reqstool_config.yml"; + + public static final String OUTPUT_ARTIFACT_DIR_TEST_RESULTS = "test_results"; + public static final String XML_IMPLEMENTATIONS = "implementations"; public static final String XML_REQUIREMENT_ANNOTATIONS = "requirement_annotations"; public static final String XML_TESTS = "tests"; - protected static final String YAML_LANG_SERVER_SCHEMA_INFO = "# yaml-language-server: $schema=https://raw.githubusercontent.com/Luftfartsverket/reqstool-client/main/src/reqstool/resources/schemas/v1/annotations.schema.json"; + protected static final String YAML_LANG_SERVER_SCHEMA_ANNOTATIONS = "# yaml-language-server: $schema=https://raw.githubusercontent.com/Luftfartsverket/reqstool-client/main/src/reqstool/resources/schemas/v1/annotations.schema.json"; + + protected static final String YAML_LANG_SERVER_SCHEMA_CONFIG = "# yaml-language-server: $schema=https://raw.githubusercontent.com/Luftfartsverket/reqstool-client/main/src/reqstool/resources/schemas/v1/reqstool_config.schema.json"; protected static final ObjectMapper yamlMapper; @@ -63,25 +87,22 @@ public class RequirementsToolMojo extends AbstractMojo { yamlMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); } - @Parameter(property = "requirementsAnnotationsFile", + @Parameter(property = "reqstool.requirementsAnnotationsFile", defaultValue = "${project.build.directory}/generated-sources/annotations/resources/annotations.yml") private File requirementsAnnotationsFile; - @Parameter(property = "svcsAnnotationsFile", + @Parameter(property = "reqstool.svcsAnnotationsFile", defaultValue = "${project.build.directory}/generated-test-sources/test-annotations/resources/annotations.yml") private File svcsAnnotationsFile; - @Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}/reqstool") + @Parameter(property = "reqstool.outputDirectory", defaultValue = "${project.build.directory}/reqstool") private File outputDirectory; - @Parameter(property = "datasetPath", defaultValue = "${project.basedir}/reqstool") + @Parameter(property = "reqstool.datasetPath", defaultValue = "${project.basedir}/reqstool") private File datasetPath; - @Parameter(property = "failsafeReportsDir", defaultValue = "${project.build.directory}/failsafe-reports") - private File failsafeReportsDir; - - @Parameter(property = "surefireReportsDir", defaultValue = "${project.build.directory}/surefire-reports") - private File surefireReportsDir; + @Parameter(property = "reqstool.testResults") + private String[] testResults; @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; @@ -108,7 +129,7 @@ public void execute() throws MojoExecutionException { } getLog().debug("Assembling and Attaching Reqstool Maven Zip Artifact"); - + getLog().info("testResults: " + Arrays.toString(testResults)); try { JsonNode implementationsNode = yamlMapper.createObjectNode(); @@ -173,12 +194,12 @@ private void writeCombinedOutputToFile(File outputFile, JsonNode combinedOutputN try (Writer writer = new PrintWriter( new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) { - writer.write(YAML_LANG_SERVER_SCHEMA_INFO + System.lineSeparator()); + writer.write(YAML_LANG_SERVER_SCHEMA_ANNOTATIONS + System.lineSeparator()); yamlMapper.writeValue(writer, combinedOutputNode); } } - private void assembleZipArtifact() throws IOException { + private void assembleZipArtifact() throws IOException, MojoExecutionException { String zipArtifactFilename = project.getBuild().getFinalName() + "-reqstool.zip"; String topLevelDir = project.getBuild().getFinalName() + "-reqstool"; @@ -187,37 +208,75 @@ private void assembleZipArtifact() throws IOException { try (FileOutputStream fos = new FileOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { - addFileToZipArtifact(zipOut, new File(datasetPath, INPUT_FILE_REQUIREMENTS_YML), new File(topLevelDir)); - addFileToZipArtifact(zipOut, new File(datasetPath, INPUT_FILESOFTWARE_VERIFICATION_CASES_YML), - new File(topLevelDir)); - addFileToZipArtifact(zipOut, new File(datasetPath, INPUT_FILE_MANUAL_VERIFICATION_RESULTS_YML), - new File(topLevelDir)); - addFileToZipArtifact(zipOut, new File(outputDirectory, OUTPUT_FILE_ANNOTATIONS_YML_FILE), - new File(topLevelDir)); - - addXmlFilesToZipArtifact(zipOut, failsafeReportsDir, - new File(topLevelDir, INPUT_DIR_TEST_RESULTS_FAILSAFE)); - addXmlFilesToZipArtifact(zipOut, surefireReportsDir, - new File(topLevelDir, INPUT_DIR_TEST_RESULTS_SUREFIRE)); - } + Map reqstoolConfigResources = new HashMap<>(); - getLog().info("Assembled zip artifact: " + zipFile.getAbsolutePath()); + File requirementsFile = new File(datasetPath, INPUT_FILE_REQUIREMENTS_YML); + if (!requirementsFile.isFile()) { + String msg = "Missing mandatory " + INPUT_FILE_REQUIREMENTS_YML + ": " + + requirementsFile.getAbsolutePath(); + throw new MojoExecutionException(msg); + } - } + addFileToZipArtifact(zipOut, requirementsFile, new File(topLevelDir)); + getLog().info("added to " + topLevelDir + ": " + requirementsFile); + reqstoolConfigResources.put("requirements", requirementsFile.getName()); - private void addXmlFilesToZipArtifact(ZipOutputStream zipOut, File directory, File targetDirectory) - throws IOException { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - addXmlFilesToZipArtifact(zipOut, file, new File(targetDirectory, file.toString())); - } - else if (file.isFile() && file.getName().toLowerCase(Locale.US).endsWith(".xml")) { - addFileToZipArtifact(zipOut, file, targetDirectory); - } + File svcsFile = new File(datasetPath, INPUT_FILE_SOFTWARE_VERIFICATION_CASES_YML); + if (svcsFile.isFile()) { + addFileToZipArtifact(zipOut, svcsFile, new File(topLevelDir)); + getLog().debug("added to " + topLevelDir + ": " + svcsFile); + reqstoolConfigResources.put("software_verification_cases", svcsFile.getName()); + } + File mvrsFile = new File(datasetPath, INPUT_FILE_MANUAL_VERIFICATION_RESULTS_YML); + if (mvrsFile.isFile()) { + addFileToZipArtifact(zipOut, mvrsFile, new File(topLevelDir)); + getLog().debug("added to " + topLevelDir + ": " + mvrsFile); + reqstoolConfigResources.put("manual_verification_results", mvrsFile.getName()); } + File annotationsZipFile = new File(outputDirectory, OUTPUT_FILE_ANNOTATIONS_YML_FILE); + if (annotationsZipFile.isFile()) { + addFileToZipArtifact(zipOut, annotationsZipFile, new File(topLevelDir)); + getLog().debug("added to " + topLevelDir + ": " + annotationsZipFile); + reqstoolConfigResources.put("annotations", annotationsZipFile.getName()); + } + + Path dir = Paths.get(project.getBasedir().toURI()); + List patterns = Arrays.stream(testResults) + .map(pattern -> "glob:" + pattern) + .collect(Collectors.toList()); + + List matchers = patterns.stream() + .map(pattern -> FileSystems.getDefault().getPathMatcher(pattern)) + .collect(Collectors.toList()); + + AtomicInteger testResultsCount = new AtomicInteger(0); + + Files.walkFileTree(dir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path relativePath = dir.relativize(file); + getLog().debug("Checking file: " + relativePath); + + if (matchers.stream().anyMatch(matcher -> matcher.matches(relativePath))) { + getLog().debug("Match found for: " + relativePath); + addFileToZipArtifact(zipOut, file.toFile(), + new File(topLevelDir, OUTPUT_ARTIFACT_DIR_TEST_RESULTS)); + testResultsCount.incrementAndGet(); + } + return FileVisitResult.CONTINUE; + } + }); + + getLog().debug("testResults values: " + Arrays.toString(testResults)); + getLog().debug("added " + testResultsCount + " test_results"); + getLog().debug("added test results: " + Arrays.toString(testResults)); + reqstoolConfigResources.put("test_results", OUTPUT_ARTIFACT_TEST_RESULTS_PATTERN); + + addReqstoolConfigYamlToZip(zipOut, new File(topLevelDir), reqstoolConfigResources); } + + getLog().info("Assembled zip artifact: " + zipFile.getAbsolutePath()); + } private void addFileToZipArtifact(ZipOutputStream zipOut, File file, File targetDirectory) throws IOException { @@ -245,7 +304,32 @@ private void attachArtifact() { String zipArtifactFilename = project.getBuild().getFinalName() + "-reqstool.zip"; File zipFile = new File(outputDirectory, zipArtifactFilename); getLog().info("Attaching artifact: " + zipFile.getName()); - projectHelper.attachArtifact(project, "zip", "reqstool", zipFile); + projectHelper.attachArtifact(project, "zip", OUTPUT_ARTIFACT_CLASSIFIER, zipFile); + } + + private void addReqstoolConfigYamlToZip(ZipOutputStream zipOut, File topLevelDir, + Map reqstoolConfigResources) throws IOException { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + Yaml yaml = new Yaml(options); + + LinkedHashMap yamlData = new LinkedHashMap<>(); + yamlData.put("language", "java"); + yamlData.put("build", "maven"); + + yamlData.put("resources", reqstoolConfigResources); + + ZipEntry zipEntry = new ZipEntry(new File(topLevelDir, OUTPUT_ARTIFACT_FILE_REQSTOOL_CONFIG_YML).toString()); + zipOut.putNextEntry(zipEntry); + + Writer writer = new OutputStreamWriter(zipOut, StandardCharsets.UTF_8); + writer.write(String.format("%s%n", YAML_LANG_SERVER_SCHEMA_CONFIG)); + writer.write(String.format("# version: %s%n", project.getVersion())); + yaml.dump(yamlData, writer); + writer.flush(); + + zipOut.closeEntry(); } } diff --git a/src/test/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojoTests.java b/src/test/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojoTests.java index f96a829..da19b3b 100644 --- a/src/test/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojoTests.java +++ b/src/test/java/se/lfv/reqstool/plugins/maven/RequirementsToolMojoTests.java @@ -50,7 +50,6 @@ void testCombine() throws IOException { @Test void testAssembleZipArtifact() throws Exception { - ClassLoader classLoader = getClass().getClassLoader(); URL resourcePath = classLoader.getResource("zip"); Path zipResourcePath = Paths.get(resourcePath.getFile()); @@ -61,35 +60,42 @@ void testAssembleZipArtifact() throws Exception { MavenProject mockProject = new MavenProject(); Build build = new Build(); build.setFinalName("test-project"); - mockProject.setBuild(build); - Field projectField = RequirementsToolMojo.class.getDeclaredField("project"); - projectField.setAccessible(true); - projectField.set(mojo, mockProject); + // Set the basedir in MavenProject using reflection + Field basedirField = mockProject.getClass().getDeclaredField("basedir"); + basedirField.setAccessible(true); + basedirField.set(mockProject, zipResourcePath.toFile()); + // Set up all required fields + Field projectField = RequirementsToolMojo.class.getDeclaredField("project"); Field outputDirectoryField = RequirementsToolMojo.class.getDeclaredField("outputDirectory"); Field datasetPathField = RequirementsToolMojo.class.getDeclaredField("datasetPath"); - Field failsafeReportsDirField = RequirementsToolMojo.class.getDeclaredField("failsafeReportsDir"); - Field surefireReportsDirField = RequirementsToolMojo.class.getDeclaredField("surefireReportsDir"); + Field testResultsField = RequirementsToolMojo.class.getDeclaredField("testResults"); + projectField.setAccessible(true); outputDirectoryField.setAccessible(true); datasetPathField.setAccessible(true); - failsafeReportsDirField.setAccessible(true); - surefireReportsDirField.setAccessible(true); + testResultsField.setAccessible(true); + projectField.set(mojo, mockProject); outputDirectoryField.set(mojo, zipResourcePath.toFile()); datasetPathField.set(mojo, zipResourcePath.toFile()); - failsafeReportsDirField.set(mojo, zipResourcePath.toFile()); - surefireReportsDirField.set(mojo, zipResourcePath.toFile()); + testResultsField.set(mojo, new String[] { "test_results/**/*.xml" }); + + // Create mandatory requirements.yml file + File reqFile = new File(zipResourcePath.toFile(), "requirements.yml"); + reqFile.getParentFile().mkdirs(); + reqFile.createNewFile(); + Files.write(reqFile.toPath(), "test content".getBytes()); + // Invoke the method Method assembleZipArtifactMethod = RequirementsToolMojo.class.getDeclaredMethod("assembleZipArtifact"); assembleZipArtifactMethod.setAccessible(true); - assembleZipArtifactMethod.invoke(mojo); + // Only check if zip file exists assertTrue(Files.exists(zipResourcePath.resolve("test-project-reqstool.zip"))); - } }