diff --git a/.vscode/settings.json b/.vscode/settings.json index e5310098..97997592 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", "editor.codeActionsOnSave": { "source.organizeImports": "explicit", "source.generate.finalModifiers": "explicit", @@ -11,7 +12,7 @@ "java.test.config": { "vmArgs": [ "-Djava.util.logging.config.file=src/test/resources/logging.properties", - "-Dcom.exasol.projectkeeper.ownVersion=4.0.0" + "-Dcom.exasol.projectkeeper.ownVersion=4.2.0" ] }, "sonarlint.connectedMode.project": { diff --git a/doc/changes/changes_4.2.1.md b/doc/changes/changes_4.2.1.md index 0d2abe42..da3c9bab 100644 --- a/doc/changes/changes_4.2.1.md +++ b/doc/changes/changes_4.2.1.md @@ -11,6 +11,7 @@ Code name: * #545: Fix parsing of Go version numbers with a `v` prefix * #548: Skip release build when preconditions are not fulfilled * #540: Improved speed of validating mentioned issues in changes file +* #553: Reduced diff in `pom.xml` for mode `update-dependencies` ## Dependency Updates diff --git a/doc/requirements/design.md b/doc/requirements/design.md index fadf2c4c..132112c5 100644 --- a/doc/requirements/design.md +++ b/doc/requirements/design.md @@ -563,13 +563,14 @@ Covers: Needs: dsn #### Incrementing the Project Version -`dsn~dependency-updater.increment-version~1` +`dsn~dependency-updater.increment-version~2` -PK increments the project's patch version. PK does not modify the version if the current version was not yet released (i.e. there is not release in the latest changelog file). +PK increments the project's patch version. PK does not modify the version if the current version was not yet released (i.e. there is not release in the latest changelog file). If PK fails to increment the version it will log a warning and continue with the process. Rationale: -Leaving the version unchanged when it was not yet released avoids surprises when running this locally. +* Leaving the version unchanged when it was not yet released avoids surprises when running this locally. +* PK could fail to increment the version in multi-module projects, because it's not located in the root `pom.xml`. Instead of failing, PK should continue with the update. It's more helpful if the user can manually increment the version in an existing pull request than to do the complete process themselves. Covers: * [`dsn~update-dependencies-mode~1`](#update-dependencies-mode) diff --git a/project-keeper-maven-plugin/src/test/java/com/exasol/projectkeeper/plugin/ProjectKeeperMojoIT.java b/project-keeper-maven-plugin/src/test/java/com/exasol/projectkeeper/plugin/ProjectKeeperMojoIT.java index ab4ca31d..b9c14b4c 100644 --- a/project-keeper-maven-plugin/src/test/java/com/exasol/projectkeeper/plugin/ProjectKeeperMojoIT.java +++ b/project-keeper-maven-plugin/src/test/java/com/exasol/projectkeeper/plugin/ProjectKeeperMojoIT.java @@ -112,18 +112,20 @@ void testVerifyRelease() throws VerificationException, IOException { containsString("E-PK-CORE-182: Release date '" + thisYear + "-??-??' has invalid format in ")); } - // [itest->dsn~dependency-updater.increment-version~1] + // [itest->dsn~dependency-updater.increment-version~2] // [itest->dsn~dependency-updater.update-dependencies~1] // [itest->dsn~dependency-updater.read-vulnerability-info~1] // [itest->dsn~dependency-updater.update-changelog~1] @Test - @DisabledOnOs(OS.WINDOWS) // Passing vulnerability JSONL via system property fails on Windows + @DisabledOnOs(OS.WINDOWS) // Passing multi-line vulnerability JSONL via system property fails on Windows void testUpgradeDependencies() throws VerificationException, IOException { - writeProjectKeeperConfig("sources:\n" + // - " - type: maven\n" + // - " path: pom.xml\n" + // - " modules:\n" + // - " - jar_artifact\n"); + writeProjectKeeperConfig(""" + sources: + - type: maven + path: pom.xml + modules: + - jar_artifact + """); final Path userGuidePath = projectDir.resolve("user_guide.md"); writeFile(userGuidePath, "artifact reference: dummy-0.1.0.jar"); diff --git a/project-keeper/error_code_config.yml b/project-keeper/error_code_config.yml index 458b12c2..4e10e748 100644 --- a/project-keeper/error_code_config.yml +++ b/project-keeper/error_code_config.yml @@ -2,4 +2,4 @@ error-tags: PK-CORE: packages: - com.exasol.projectkeeper - highest-index: 192 + highest-index: 196 diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/DependencyUpdater.java b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/DependencyUpdater.java index 0f65c77c..0725d4e7 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/DependencyUpdater.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/DependencyUpdater.java @@ -65,7 +65,7 @@ public static DependencyUpdater create(final ProjectKeeper projectKeeper, final * * @return {@code true} if the process succeeded. */ - // [impl->dsn~dependency-updater.increment-version~1] + // [impl->dsn~dependency-updater.increment-version~2] // [impl->dsn~dependency-updater.update-changelog~1] public boolean updateDependencies() { final String version = incrementProjectVersion(); diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementor.java b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementor.java index c6969a35..8fa982df 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementor.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementor.java @@ -7,7 +7,8 @@ import java.util.Objects; import java.util.Optional; -import org.apache.maven.model.Model; +import org.w3c.dom.Document; +import org.w3c.dom.Node; import com.exasol.errorreporting.ExaError; import com.exasol.projectkeeper.Logger; @@ -15,13 +16,12 @@ import com.exasol.projectkeeper.sources.analyze.generic.*; import com.exasol.projectkeeper.validators.changesfile.ChangesFile; import com.exasol.projectkeeper.validators.changesfile.ChangesFileIO; -import com.exasol.projectkeeper.validators.pom.PomFileIO; import com.vdurmont.semver4j.Semver; /** * This class can increment the project's version. */ -// [impl->dsn~dependency-updater.increment-version~1] +// [impl->dsn~dependency-updater.increment-version~2] class ProjectVersionIncrementor { private static final ZoneId UTC_ZONE = ZoneId.of("UTC"); private final ProjectKeeperConfig config; @@ -30,23 +30,23 @@ class ProjectVersionIncrementor { private final Clock clock; private final Path projectDir; private final Logger logger; - private final PomFileIO pomFileIO; + private final XmlDocumentIO xmlFileIO; private final CommandExecutor commandExecutor; ProjectVersionIncrementor(final ProjectKeeperConfig config, final Logger logger, final Path projectDir, final String currentProjectVersion) { - this(config, logger, projectDir, currentProjectVersion, new ChangesFileIO(), new PomFileIO(), + this(config, logger, projectDir, currentProjectVersion, new ChangesFileIO(), new XmlDocumentIO(), new CommandExecutor(), Clock.systemUTC()); } ProjectVersionIncrementor(final ProjectKeeperConfig config, final Logger logger, final Path projectDir, - final String currentProjectVersion, final ChangesFileIO changesFileIO, final PomFileIO pomFileIO, + final String currentProjectVersion, final ChangesFileIO changesFileIO, final XmlDocumentIO xmlFileIO, final CommandExecutor commandExecutor, final Clock clock) { this.config = config; this.logger = logger; this.projectDir = projectDir; this.changesFileIO = changesFileIO; - this.pomFileIO = pomFileIO; + this.xmlFileIO = xmlFileIO; this.commandExecutor = commandExecutor; this.clock = clock; this.currentProjectVersion = Objects.requireNonNull(currentProjectVersion, "currentProjectVersion"); @@ -86,20 +86,22 @@ private LocalDate today() { * @return the new, incremented version */ String incrementProjectVersion() { - final Path path = getPomPath(); - final Model pom = pomFileIO.readPom(path); - if (!this.currentProjectVersion.equals(pom.getVersion())) { - throw new IllegalStateException(ExaError.messageBuilder("E-PK-CORE-174").message( - "Inconsistent project version {{version in pom file}} found in pom {{pom file path}}, expected {{expected version}}.", - pom.getVersion(), path, currentProjectVersion).ticketMitigation().toString()); - } - final String nextVersion = incrementVersion(pom); + final String nextVersion = getIncrementedVersion(currentProjectVersion); + updatePomVersion(nextVersion); if (usesReferenceCheckerPlugin()) { updateReferences(); } return nextVersion; } + private void updatePomVersion(final String nextVersion) { + final Path path = getPomPath(); + logger.info("Incrementing version from " + currentProjectVersion + " to " + nextVersion + " in POM " + path); + final Document pom = xmlFileIO.read(path); + incrementVersion(path, pom, nextVersion); + xmlFileIO.write(pom, path); + } + private boolean usesReferenceCheckerPlugin() { return config.getSources().stream().anyMatch(source -> source.getModules().contains(JAR_ARTIFACT)); } @@ -111,13 +113,24 @@ private void updateReferences() { commandExecutor.execute(command); } - private String incrementVersion(final Model pom) { - final String nextVersion = getIncrementedVersion(currentProjectVersion); - logger.info("Incrementing version from " + currentProjectVersion + " to " + nextVersion + " in POM " - + pom.getPomFile()); - pom.setVersion(nextVersion); - writePom(pom); - return nextVersion; + private void incrementVersion(final Path path, final Document pom, final String nextVersion) { + final Optional versionNode = findVersionNode(pom); + if (versionNode.isEmpty()) { + logger.warn(ExaError.messageBuilder("W-PK-CORE-196") + .message("No version node found in pom file {{pom file path}}.", path) + .mitigation("Please update the version to {{next version}} manually.", nextVersion).toString()); + return; + } + if (!this.currentProjectVersion.equals(versionNode.get().getTextContent())) { + throw new IllegalStateException(ExaError.messageBuilder("E-PK-CORE-174").message( + "Inconsistent project version {{version in pom file}} found in pom {{pom file path}}, expected {{expected version}}.", + versionNode.get().getTextContent(), path, currentProjectVersion).ticketMitigation().toString()); + } + versionNode.get().setTextContent(nextVersion); + } + + private Optional findVersionNode(final Document pom) { + return xmlFileIO.runXPath(pom, "/project/version"); } static String getIncrementedVersion(final String version) { @@ -125,11 +138,6 @@ static String getIncrementedVersion(final String version) { return current.nextPatch().toString(); } - private void writePom(final Model pom) { - final Path path = getPomPath(); - pomFileIO.writePom(pom, path); - } - private Path getPomPath() { return projectDir.resolve("pom.xml"); } diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIO.java b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIO.java new file mode 100644 index 00000000..986ef4b9 --- /dev/null +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIO.java @@ -0,0 +1,49 @@ +package com.exasol.projectkeeper.dependencyupdate; + +import java.nio.file.Path; +import java.util.Optional; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import com.exasol.projectkeeper.validators.pom.io.PomFileReader; +import com.exasol.projectkeeper.validators.pom.io.PomFileWriter; +import com.exasol.projectkeeper.xpath.XPathErrorHandlingWrapper; + +/** + * This class provides read and write access to XML documents. + *

+ * The advantage of this class is that it can be easily mocked in tests and it preserves comments when writing XML. + */ +class XmlDocumentIO { + /** + * Read an XML document from a file. + * + * @param path path to the file + * @return XML document + */ + Document read(final Path path) { + return PomFileReader.parse(path); + } + + /** + * Write an XML document to a file. + * + * @param document XML document + * @param path path to the file + */ + void write(final Document document, final Path path) { + PomFileWriter.writeFile(document, path); + } + + /** + * Run an XPath query on an XML document and return the first result. + * + * @param current current node + * @param xPath XPath query + * @return first result of the query + */ + Optional runXPath(final Node current, final String xPath) { + return Optional.ofNullable(XPathErrorHandlingWrapper.runXPath(current, xPath)); + } +} diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/github/FileContentProvider.java b/project-keeper/src/main/java/com/exasol/projectkeeper/github/FileContentProvider.java index 7f7391df..b5667ad2 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/github/FileContentProvider.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/github/FileContentProvider.java @@ -40,7 +40,7 @@ static WorkflowOutput create(final Path outputPath) { @Override public void publish(final String key, final String value) { final String content = formatter.format(key, value); - LOG.info(() -> "Publishing content '" + content + "'"); + LOG.finest(() -> "Publishing content '" + content + "'"); write(content + "\n"); } diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/github/NullContentProvider.java b/project-keeper/src/main/java/com/exasol/projectkeeper/github/NullContentProvider.java index 211b23a9..98fe66fd 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/github/NullContentProvider.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/github/NullContentProvider.java @@ -11,7 +11,7 @@ class NullContentProvider implements WorkflowOutput { @Override public void publish(final String key, final String value) { - LOG.fine(() -> "Publishing key/value pair '" + key + "' = '" + value + "'"); + LOG.finest(() -> "Publishing key/value pair '" + key + "' = '" + value + "'"); } @Override diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomFileValidator.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomFileValidator.java index 7d376b48..9629c710 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomFileValidator.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomFileValidator.java @@ -62,7 +62,7 @@ private XPath() { } private final Path projectDirectory; - final Collection enabledModules; + private final Collection enabledModules; private final Path pomFilePath; private final ParentPomRef parentPomRef; private final RepoInfo repoInfo; diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomRenderer.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomRenderer.java deleted file mode 100644 index 68e33a74..00000000 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/pom/PomRenderer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.exasol.projectkeeper.validators.pom; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; - -import javax.xml.XMLConstants; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.w3c.dom.Node; - -import com.exasol.errorreporting.ExaError; - -/** - * This class renders a XML document as string. That's useful for debugging and testing. - */ -public class PomRenderer { - - private PomRenderer() { - // empty on purpose - } - - /** - * Render a XML document as string - * - * @param xmlDocument XML document to render - * @return XML string - */ - public static String renderPom(final Node xmlDocument) { - try { - final var transformerFactory = TransformerFactory.newInstance(); - transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); - final Transformer transformer = transformerFactory.newTransformer(); - final var domSource = new DOMSource(xmlDocument); - final ByteArrayOutputStream targetStream = new ByteArrayOutputStream(); - final var streamResult = new StreamResult(targetStream); - transformer.transform(domSource, streamResult); - return targetStream.toString(StandardCharsets.UTF_8); - } catch (final TransformerException exception) { - throw new IllegalStateException( - ExaError.messageBuilder("E-PK-CORE-74").message("Failed to render XML document.").toString(), - exception); - } - } -} diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/release/github/GitHubAdapter.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/release/github/GitHubAdapter.java index d58ba7c1..ffe81798 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/release/github/GitHubAdapter.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/release/github/GitHubAdapter.java @@ -46,16 +46,19 @@ public IssueState getIssueState(final int issueNumber) { final Issue issue = gitHubConnectionProvider.connect().repos().get(new Coordinates.Simple(owner, repoName)) .issues().get(issueNumber); final Issue.Smart smartIssue = new Issue.Smart(issue); + final IssueState state = getIssueState(issueNumber, smartIssue); + LOG.fine(() -> "GitHub issue #" + issueNumber + " has state " + state); + return state; + } + + private IssueState getIssueState(final int issueNumber, final Issue.Smart smartIssue) { try { if (!smartIssue.exists()) { - LOG.fine(() -> "GitHub issue #" + issueNumber + " does not exist."); return IssueState.MISSING; } if (smartIssue.isOpen()) { - LOG.fine(() -> "GitHub issue #" + issueNumber + " is still open."); return IssueState.OPEN; } else { - LOG.fine(() -> "GitHub issue #" + issueNumber + " is closed."); return IssueState.CLOSED; } } catch (final IOException exception) { diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementorTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementorTest.java index badd883c..a36cd1fe 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementorTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/ProjectVersionIncrementorTest.java @@ -2,8 +2,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.verify; @@ -11,10 +10,8 @@ import java.nio.file.Path; import java.time.*; -import java.util.List; -import java.util.Set; +import java.util.*; -import org.apache.maven.model.Model; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -22,6 +19,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.w3c.dom.Document; +import org.w3c.dom.Node; import com.exasol.projectkeeper.Logger; import com.exasol.projectkeeper.shared.config.*; @@ -29,9 +28,8 @@ import com.exasol.projectkeeper.sources.analyze.generic.ShellCommand; import com.exasol.projectkeeper.validators.changesfile.ChangesFile; import com.exasol.projectkeeper.validators.changesfile.ChangesFileIO; -import com.exasol.projectkeeper.validators.pom.PomFileIO; -// [utest->dsn~dependency-updater.increment-version~1] +// [utest->dsn~dependency-updater.increment-version~2] @ExtendWith(MockitoExtension.class) class ProjectVersionIncrementorTest { @@ -46,10 +44,15 @@ class ProjectVersionIncrementorTest { @Mock private ChangesFileIO changesFileIOMock; @Mock - private PomFileIO pomFileIOMock; + private XmlDocumentIO xmlDocumentIOMock; @Mock private CommandExecutor commandExecutorMock; + @Mock + Document pomModel; + @Mock + Node versionNode; + @ParameterizedTest @CsvSource(nullValues = "NULL", value = { "NULL, false", "invalid data, false", "2007-??-??, false", "2024-??-??, false", "2007-01-01, true", "2007-12-02, true", "2007-12-03, false", "2007-12-04, false", @@ -63,11 +66,8 @@ void isCurrentVersionReleased(final String releaseDate, final boolean expectedRe @Test void incrementProjectVersionFailsForInconsistentVersionInPom() { - final Model pomModel = new Model(); - pomModel.setVersion("1.2.2"); - when(pomFileIOMock.readPom(POM_PATH)).thenReturn(pomModel); + simulatePomVersion("1.2.2"); final ProjectVersionIncrementor testee = testee(configWithoutJarArtifact()); - final IllegalStateException exception = assertThrows(IllegalStateException.class, testee::incrementProjectVersion); assertThat(exception.getMessage(), @@ -75,27 +75,48 @@ void incrementProjectVersionFailsForInconsistentVersionInPom() { + "', expected '1.2.3'.")); } + @Test + void incrementProjectVersionLogsWarningForMissingVersionElement() { + simulatePomVersion(null); + final ProjectVersionIncrementor testee = testee(configWithoutJarArtifact()); + assertDoesNotThrow(testee::incrementProjectVersion); + verify(loggerMock).warn("W-PK-CORE-196: No version node found in pom file '" + POM_PATH + + "'. Please update the version to '1.2.4' manually."); + } + + private void simulatePomVersion(final String version) { + when(xmlDocumentIOMock.read(POM_PATH)).thenReturn(pomModel); + if (version != null) { + when(versionNode.getTextContent()).thenReturn(version); + when(xmlDocumentIOMock.runXPath(same(pomModel), eq("/project/version"))) + .thenReturn(Optional.of(versionNode)); + } else { + when(xmlDocumentIOMock.runXPath(same(pomModel), eq("/project/version"))).thenReturn(Optional.empty()); + } + } + @ParameterizedTest @CsvSource({ "0.0.0, 0.0.1", "0.1.1, 0.1.2", "1.2.1, 1.2.2", "9.9.9, 9.9.10", "1.2.99, 1.2.100", "99.34.12345, 99.34.12346" }) void incrementProjectVersion(final String currentVersion, final String expectedNextVersion) { - final Model pomModel = new Model(); - pomModel.setVersion(currentVersion); - when(pomFileIOMock.readPom(POM_PATH)).thenReturn(pomModel); + simulatePomVersion(currentVersion); final String newVersion = testee(configWithoutJarArtifact(), currentVersion).incrementProjectVersion(); - assertAll(() -> assertThat(newVersion, equalTo(expectedNextVersion)), - () -> assertThat(pomModel.getVersion(), equalTo(newVersion))); - verify(pomFileIOMock).writePom(same(pomModel), eq(POM_PATH)); + assertThat(newVersion, equalTo(expectedNextVersion)); + verifyPomVersionUpdated(expectedNextVersion); + } + + private void verifyPomVersionUpdated(final String expectedNextVersion) { + verify(versionNode).setTextContent(expectedNextVersion); + verify(xmlDocumentIOMock).write(same(pomModel), eq(POM_PATH)); } @Test void incrementProjectVersionWithJarArtifactUpdatesReferences() { - final Model pomModel = new Model(); - pomModel.setVersion(CURRENT_PROJECT_VERSION); - when(pomFileIOMock.readPom(POM_PATH)).thenReturn(pomModel); + simulatePomVersion(CURRENT_PROJECT_VERSION); final String newVersion = testee(configWithJarArtifact(), CURRENT_PROJECT_VERSION).incrementProjectVersion(); - assertAll(() -> assertThat(newVersion, equalTo("1.2.4")), - () -> assertThat(pomModel.getVersion(), equalTo(newVersion)), () -> assertMavenExecuted()); + assertAll(() -> assertThat(newVersion, equalTo("1.2.4")), // + () -> assertMavenExecuted()); + verifyPomVersionUpdated("1.2.4"); } private void assertMavenExecuted(final String... mavenArguments) { @@ -121,7 +142,7 @@ private ProjectVersionIncrementor testee(final ProjectKeeperConfig config) { private ProjectVersionIncrementor testee(final ProjectKeeperConfig config, final String currentProjectVersion) { return new ProjectVersionIncrementor(config, loggerMock, PROJECT_DIR, currentProjectVersion, changesFileIOMock, - pomFileIOMock, commandExecutorMock, fixedClock); + xmlDocumentIOMock, commandExecutorMock, fixedClock); } private ProjectKeeperConfig configWithJarArtifact() { diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIOTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIOTest.java new file mode 100644 index 00000000..c947c9ce --- /dev/null +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/dependencyupdate/XmlDocumentIOTest.java @@ -0,0 +1,96 @@ +package com.exasol.projectkeeper.dependencyupdate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import javax.xml.parsers.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +class XmlDocumentIOTest { + + @TempDir + Path tempDir; + + @Test + void read() throws IOException { + final Path file = tempDir.resolve("file.xml"); + Files.writeString(file, "content"); + final Document document = testee().read(file); + assertThat(document.getFirstChild().getNodeName(), equalTo("root")); + } + + @Test + void write() throws ParserConfigurationException, IOException { + final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + final Document doc = docBuilder.newDocument(); + doc.appendChild(doc.createElement("root")); + final Path file = tempDir.resolve("file.xml"); + testee().write(doc, file); + assertThat(Files.readString(file), equalTo(normalizeLineEndings(""" + + + """))); + } + + @Test + void readWritePreservesComments() throws IOException { + final Path file = tempDir.resolve("file.xml"); + final String xmlContent = normalizeLineEndings(""" + + content + """); + Files.writeString(file, xmlContent); + final XmlDocumentIO io = testee(); + io.write(io.read(file), file); + assertThat(Files.readString(file), equalTo(xmlContent)); + } + + private String normalizeLineEndings(final String content) { + return content.replace("\n", System.lineSeparator()); + } + + @Test + void runXPath() throws IOException { + final Path file = tempDir.resolve("file.xml"); + Files.writeString(file, "content"); + final Document document = testee().read(file); + assertThat(testee().runXPath(document, "/root").get().getNodeName(), equalTo("root")); + } + + @Test + void runXPathNoElementFound() throws IOException { + final Path file = tempDir.resolve("file.xml"); + Files.writeString(file, "content"); + final Document document = testee().read(file); + assertThat(testee().runXPath(document, "/wrong").isPresent(), is(false)); + } + + @Test + void runXPathReturnsFirstMatch() throws IOException { + final Path file = tempDir.resolve("file.xml"); + Files.writeString(file, """ + + + + + """); + final Document document = testee().read(file); + final Optional result = testee().runXPath(document, "/root/child"); + assertThat(result.get().getAttributes().getNamedItem("name").getNodeValue(), equalTo("child1")); + } + + private XmlDocumentIO testee() { + return new XmlDocumentIO(); + } +}