From 57ab156be0f6a4be8b0591dc4074fe0c77426e21 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 9 Feb 2024 19:27:06 +0100 Subject: [PATCH] Add the --ignore-inherited option to ignore build file artifacts with an inherited version --- CHANGELOG.md | 1 + .../alexisjehan/mvncheck/Application.java | 93 ++++++--- .../alexisjehan/mvncheck/core/Service.java | 21 ++ .../alexisjehan/mvncheck/ApplicationIT.java | 2 +- .../alexisjehan/mvncheck/ApplicationTest.java | 195 +++++++++++++++++- .../mvncheck/core/ServiceTest.java | 90 +++++++- 6 files changed, 365 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b08399d..ba23ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features - Display artifacts with an inherited version in parentheses +- Add the `--ignore-inherited` option to ignore build file artifacts with an inherited version ### Notes - Update the `maven-core` dependency to `3.9.6` diff --git a/src/main/java/com/github/alexisjehan/mvncheck/Application.java b/src/main/java/com/github/alexisjehan/mvncheck/Application.java index b299e3d..db0ab19 100644 --- a/src/main/java/com/github/alexisjehan/mvncheck/Application.java +++ b/src/main/java/com/github/alexisjehan/mvncheck/Application.java @@ -57,57 +57,63 @@ public final class Application { /** - *

Title.

- * @since 1.4.0 + *

Maximum depth option long name.

+ * @since 1.1.0 */ - private static final String TITLE = Constants.NAME + " " + Constants.VERSION - + " (built with Maven " + MavenUtils.VERSION + " and Gradle " + GradleUtils.VERSION + ")"; + static final String OPTION_MAX_DEPTH = "max-depth"; /** - *

Description.

+ *

Help option long name.

* @since 1.0.0 */ - private static final String DESCRIPTION = "Check for artifact updates of every " - + ToString.toString(BuildFileType.MAVEN.getFileName()) + ", " - + ToString.toString(BuildFileType.GRADLE_GROOVY.getFileName()) + " and " - + ToString.toString(BuildFileType.GRADLE_KOTLIN.getFileName()) + " build files in the given or current " - + "path recursively."; + static final String OPTION_HELP = "help"; /** - *

Command name.

- * @since 1.0.0 + *

Ignore inherited option long name.

+ * @since 1.5.0 */ - private static final String COMMAND_NAME = "mvnchk"; + static final String OPTION_IGNORE_INHERITED = "ignore-inherited"; /** - *

Maximum depth option long name.

- * @since 1.1.0 + *

Ignore snapshots option long name.

+ * @since 1.0.0 */ - private static final String OPTION_MAX_DEPTH = "max-depth"; + static final String OPTION_IGNORE_SNAPSHOTS = "ignore-snapshots"; /** - *

Help option long name.

+ *

Short option long name.

* @since 1.0.0 */ - private static final String OPTION_HELP = "help"; + static final String OPTION_SHORT = "short"; /** - *

Ignore snapshots option long name.

+ *

Version option long name.

* @since 1.0.0 */ - private static final String OPTION_IGNORE_SNAPSHOTS = "ignore-snapshots"; + static final String OPTION_VERSION = "version"; /** - *

Short option long name.

+ *

Title.

+ * @since 1.4.0 + */ + private static final String TITLE = Constants.NAME + " " + Constants.VERSION + + " (built with Maven " + MavenUtils.VERSION + " and Gradle " + GradleUtils.VERSION + ")"; + + /** + *

Description.

* @since 1.0.0 */ - private static final String OPTION_SHORT = "short"; + private static final String DESCRIPTION = "Check for artifact updates of every " + + ToString.toString(BuildFileType.MAVEN.getFileName()) + ", " + + ToString.toString(BuildFileType.GRADLE_GROOVY.getFileName()) + " and " + + ToString.toString(BuildFileType.GRADLE_KOTLIN.getFileName()) + " build files in the given or current " + + "path recursively."; /** - *

Version option long name.

+ *

Command name.

* @since 1.0.0 */ - private static final String OPTION_VERSION = "version"; + private static final String COMMAND_NAME = "mvnchk"; /** *

Default value depending on whether ANSI should be enabled or not.

@@ -146,6 +152,12 @@ public final class Application { false, "Display help information" ); + options.addOption( + null, + OPTION_IGNORE_INHERITED, + false, + "Ignore build file artifacts with an inherited version" + ); options.addOption( "i", OPTION_IGNORE_SNAPSHOTS, @@ -245,6 +257,7 @@ void run(final String... args) { commandLine.hasOption(OPTION_MAX_DEPTH) ? Integer.parseUnsignedInt(commandLine.getOptionValue(OPTION_MAX_DEPTH)) : DEFAULT_MAX_DEPTH, + commandLine.hasOption(OPTION_IGNORE_INHERITED), commandLine.hasOption(OPTION_IGNORE_SNAPSHOTS), commandLine.hasOption(OPTION_SHORT) ); @@ -265,7 +278,11 @@ void run(final String... args) { * @since 1.0.0 */ @Deprecated(since = "1.1.0") - void run(final Path path, final boolean ignoreSnapshots, final boolean short0) throws IOException { + void run( + final Path path, + final boolean ignoreSnapshots, + final boolean short0 + ) throws IOException { run(path, DEFAULT_MAX_DEPTH, ignoreSnapshots, short0); } @@ -278,11 +295,35 @@ void run(final Path path, final boolean ignoreSnapshots, final boolean short0) t * @throws IOException might occur with input/output operations * @throws NullPointerException if the path is {@code null} * @throws IllegalArgumentException if the maximum depth is lower than {@code 0} + * @deprecated since 1.5.0, use {@link #run(Path, int, boolean, boolean, boolean)} instead * @since 1.1.0 */ + @Deprecated(since = "1.5.0") + void run( + final Path path, + final int maxDepth, + final boolean ignoreSnapshots, + final boolean short0 + ) throws IOException { + run(path, maxDepth, false, ignoreSnapshots, short0); + } + + /** + *

Run the program.

+ * @param path a path + * @param maxDepth a maximum depth + * @param ignoreInherited {@code true} if build file artifacts with an inherited version should be ignored + * @param ignoreSnapshots {@code true} if build file artifacts with a snapshot version should be ignored + * @param short0 {@code true} if only build files with at least one artifact update should be shown + * @throws IOException might occur with input/output operations + * @throws NullPointerException if the path is {@code null} + * @throws IllegalArgumentException if the maximum depth is lower than {@code 0} + * @since 1.5.0 + */ void run( final Path path, final int maxDepth, + final boolean ignoreInherited, final boolean ignoreSnapshots, final boolean short0 ) throws IOException { @@ -303,7 +344,7 @@ void run( final List artifactUpdateVersions; try { final var build = service.findBuild(buildFile); - artifactUpdateVersions = service.findArtifactUpdateVersions(build, ignoreSnapshots); + artifactUpdateVersions = service.findArtifactUpdateVersions(build, ignoreInherited, ignoreSnapshots); } catch (final BuildResolveException | ArtifactAvailableVersionsResolveException e) { outputStream.println(Ansi.ansi().fgBrightRed().a(toString(file)).reset()); outputStream.println(Ansi.ansi().fgBrightRed().a(toString(e)).reset()); diff --git a/src/main/java/com/github/alexisjehan/mvncheck/core/Service.java b/src/main/java/com/github/alexisjehan/mvncheck/core/Service.java index fb571aa..d13b536 100644 --- a/src/main/java/com/github/alexisjehan/mvncheck/core/Service.java +++ b/src/main/java/com/github/alexisjehan/mvncheck/core/Service.java @@ -206,11 +206,31 @@ public Build findBuild(final BuildFile buildFile) { * @return the {@link List} of artifact update versions * @throws IOException might occur with input/output operations * @throws NullPointerException if the build is {@code null} + * @deprecated since 1.5.0, use {@link #findArtifactUpdateVersions(Build, boolean, boolean)} instead * @since 1.0.0 */ + @Deprecated(since = "1.5.0") public List findArtifactUpdateVersions( final Build build, final boolean ignoreSnapshots + ) throws IOException { + return findArtifactUpdateVersions(build, false, ignoreSnapshots); + } + + /** + *

Find a {@link List} of artifact update versions for the given build.

+ * @param build a build + * @param ignoreInherited {@code true} if build file artifacts with an inherited version should be ignored + * @param ignoreSnapshots {@code true} if build file artifacts with a snapshot version should be ignored + * @return the {@link List} of artifact update versions + * @throws IOException might occur with input/output operations + * @throws NullPointerException if the build is {@code null} + * @since 1.5.0 + */ + public List findArtifactUpdateVersions( + final Build build, + final boolean ignoreInherited, + final boolean ignoreSnapshots ) throws IOException { Ensure.notNull("build", build); final var artifactFilter = new CompositeArtifactFilter( @@ -220,6 +240,7 @@ public List findArtifactUpdateVersions( return build.getArtifacts() .parallelStream() .filter(artifactFilter::accept) + .filter(artifact -> !ignoreInherited || !artifact.isVersionInherited()) .filter( artifact -> artifact.getOptionalVersion() .filter(version -> !ignoreSnapshots || !VersionFilter.SNAPSHOT.accept(version)) diff --git a/src/test/java/com/github/alexisjehan/mvncheck/ApplicationIT.java b/src/test/java/com/github/alexisjehan/mvncheck/ApplicationIT.java index c1d1857..f671f1f 100644 --- a/src/test/java/com/github/alexisjehan/mvncheck/ApplicationIT.java +++ b/src/test/java/com/github/alexisjehan/mvncheck/ApplicationIT.java @@ -64,7 +64,7 @@ void testRun(@TempDir final Path tmpDirectory) throws IOException { try (var outputStream = new ByteArrayOutputStream()) { try (var printStream = new PrintStream(outputStream)) { final var application = new Application(printStream); - application.run(tmpDirectory, 1, false, false); + application.run(tmpDirectory, 1, false, false, false); } assertThat(outputStream.toString()).matches( "^" diff --git a/src/test/java/com/github/alexisjehan/mvncheck/ApplicationTest.java b/src/test/java/com/github/alexisjehan/mvncheck/ApplicationTest.java index af31957..af9a1e1 100644 --- a/src/test/java/com/github/alexisjehan/mvncheck/ApplicationTest.java +++ b/src/test/java/com/github/alexisjehan/mvncheck/ApplicationTest.java @@ -71,7 +71,7 @@ void testConstructorInvalid() { @Test @Deprecated - void testRunDeprecated() throws IOException { + void testRunDeprecated1() throws IOException { final var path1 = Path.of("path1"); final var path2 = Path.of("path2"); final var path3 = Path.of("path3"); @@ -105,9 +105,21 @@ void testRunDeprecated() throws IOException { .thenReturn(build2); Mockito.when(mockedService.findBuild(Mockito.argThat(buildFile3::equals))) .thenThrow(BuildResolveException.class); - Mockito.when(mockedService.findArtifactUpdateVersions(Mockito.argThat(build1::equals), Mockito.anyBoolean())) + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build1::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) .thenReturn(List.of()); - Mockito.when(mockedService.findArtifactUpdateVersions(Mockito.argThat(build2::equals), Mockito.anyBoolean())) + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build2::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) .thenReturn(List.of(new ArtifactUpdateVersion(artifact, "2.0.0"))); try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { try (var mockedStaticGithubUtils = Mockito.mockStatic(GithubUtils.class)) { @@ -165,7 +177,7 @@ void testRunDeprecated() throws IOException { @Test @Deprecated - void testRunDeprecatedInvalid() { + void testRunDeprecated1Invalid() { try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { final var application = new Application(printStream); assertThatNullPointerException().isThrownBy( @@ -181,7 +193,8 @@ void testRunDeprecatedInvalid() { } @Test - void testRun() throws IOException { + @Deprecated + void testRunDeprecated2() throws IOException { final var path1 = Path.of("path1"); final var path2 = Path.of("path2"); final var path3 = Path.of("path3"); @@ -215,9 +228,21 @@ void testRun() throws IOException { .thenReturn(build2); Mockito.when(mockedService.findBuild(Mockito.argThat(buildFile3::equals))) .thenThrow(BuildResolveException.class); - Mockito.when(mockedService.findArtifactUpdateVersions(Mockito.argThat(build1::equals), Mockito.anyBoolean())) + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build1::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) .thenReturn(List.of()); - Mockito.when(mockedService.findArtifactUpdateVersions(Mockito.argThat(build2::equals), Mockito.anyBoolean())) + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build2::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) .thenReturn(List.of(new ArtifactUpdateVersion(artifact, "2.0.0"))); try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { try (var mockedStaticGithubUtils = Mockito.mockStatic(GithubUtils.class)) { @@ -274,7 +299,8 @@ void testRun() throws IOException { } @Test - void testRunInvalid() { + @Deprecated + void testRunDeprecated2Invalid() { try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { final var application = new Application(printStream); assertThatNullPointerException().isThrownBy( @@ -292,6 +318,159 @@ void testRunInvalid() { } } + @Test + void testRun() throws IOException { + final var path1 = Path.of("path1"); + final var path2 = Path.of("path2"); + final var path3 = Path.of("path3"); + final var buildFile1 = new BuildFile(BuildFileType.MAVEN, Path.of("pom.xml")); + final var buildFile2 = new BuildFile(BuildFileType.GRADLE_GROOVY, Path.of("build.gradle")); + final var buildFile3 = new BuildFile(BuildFileType.GRADLE_KOTLIN, Path.of("build.gradle.kts")); + final var artifact = new Artifact<>( + MavenArtifactType.DEPENDENCY, + new ArtifactIdentifier("foo-group-id", "foo-artifact-id"), + "1.0.0", + true + ); + final var build1 = new Build( + buildFile1, + List.of(), + List.of(artifact) + ); + final var build2 = new Build( + buildFile2, + List.of(), + List.of(artifact) + ); + Mockito.when(mockedService.findBuildFiles(Mockito.argThat(path1::equals), Mockito.anyInt())) + .thenReturn(List.of()); + Mockito.when(mockedService.findBuildFiles(Mockito.argThat(path2::equals), Mockito.anyInt())) + .thenReturn(List.of(buildFile1)); + Mockito.when(mockedService.findBuildFiles(Mockito.argThat(path3::equals), Mockito.anyInt())) + .thenReturn(List.of(buildFile1, buildFile2, buildFile3)); + Mockito.when(mockedService.findBuild(Mockito.argThat(buildFile1::equals))) + .thenReturn(build1); + Mockito.when(mockedService.findBuild(Mockito.argThat(buildFile2::equals))) + .thenReturn(build2); + Mockito.when(mockedService.findBuild(Mockito.argThat(buildFile3::equals))) + .thenThrow(BuildResolveException.class); + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build1::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) + .thenReturn(List.of()); + Mockito.when( + mockedService.findArtifactUpdateVersions( + Mockito.argThat(build2::equals), + Mockito.anyBoolean(), + Mockito.anyBoolean() + ) + ) + .thenReturn(List.of(new ArtifactUpdateVersion(artifact, "2.0.0"))); + try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { + try (var mockedStaticGithubUtils = Mockito.mockStatic(GithubUtils.class)) { + mockedStaticGithubUtils.when( + () -> GithubUtils.retrieveOptionalLatestReleaseName( + Mockito.notNull(), + Mockito.notNull() + ) + ) + .thenReturn( + Optional.of("1.0.0"), + Optional.of("2.0.0"), + Optional.empty() + ); + try (var mockedStaticApplication = Mockito.mockStatic(Application.class)) { + mockedStaticApplication.when(Application::createService) + .thenReturn(mockedService); + mockedStaticApplication.when(Application::getCurrentVersion) + .thenReturn( + "1.0.0", + "1.0.0", + null + ); + final var application = new Application(printStream); + assertThatNoException().isThrownBy( + application::run + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_MAX_DEPTH, "0") + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_HELP) + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_IGNORE_INHERITED) + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_IGNORE_SNAPSHOTS) + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_SHORT) + ); + assertThatNoException().isThrownBy( + () -> application.run("--" + Application.OPTION_VERSION) + ); + assertThatNoException().isThrownBy( + () -> application.run("directory_not-found") + ); + assertThatNoException().isThrownBy( + () -> application.run(path1.toString()) + ); + assertThatNoException().isThrownBy( + () -> application.run(path2.toString()) + ); + assertThatNoException().isThrownBy( + () -> application.run(path3.toString()) + ); + assertThatNoException().isThrownBy( + () -> application.run(path1, 0, true, true, true) + ); + assertThatNoException().isThrownBy( + () -> application.run(path2, 0, true, true, true) + ); + assertThatNoException().isThrownBy( + () -> application.run(path3, 0, true, true, true) + ); + } + } + } + } + + @Test + void testRunInvalid() { + try (var printStream = new PrintStream(OutputStream.nullOutputStream())) { + final var application = new Application(printStream); + assertThatNullPointerException().isThrownBy( + () -> application.run((String[]) null) + ); + assertThatNullPointerException().isThrownBy( + () -> application.run((String) null) + ); + assertThatNullPointerException().isThrownBy( + () -> application.run( + null, + 0, + false, + false, + false + ) + ); + assertThatIllegalArgumentException().isThrownBy( + () -> application.run( + Path.of("path"), + -1, + false, + false, + false + ) + ); + } + } + @Test void testCreateService() { assertThatNoException().isThrownBy(Application::createService); diff --git a/src/test/java/com/github/alexisjehan/mvncheck/core/ServiceTest.java b/src/test/java/com/github/alexisjehan/mvncheck/core/ServiceTest.java index be9b15d..379c971 100644 --- a/src/test/java/com/github/alexisjehan/mvncheck/core/ServiceTest.java +++ b/src/test/java/com/github/alexisjehan/mvncheck/core/ServiceTest.java @@ -337,7 +337,8 @@ void testFindBuildInvalid() throws IOException { } @Test - void testFindArtifactUpdateVersions() throws IOException { + @Deprecated + void testFindArtifactUpdateVersionsDeprecated() throws IOException { final var service = new Service( Set.of(mockedMavenBuildResolver, mockedGradleBuildResolver), mockedArtifactAvailableVersionsResolver @@ -393,12 +394,97 @@ void testFindArtifactUpdateVersions() throws IOException { } @Test - void testFindArtifactUpdateVersionsInvalid() throws IOException { + @Deprecated + void testFindArtifactUpdateVersionsDeprecatedInvalid() throws IOException { final var service = new Service(mockedMavenSession); assertThatNullPointerException() .isThrownBy(() -> service.findArtifactUpdateVersions(null, false)); } + @Test + void testFindArtifactUpdateVersions() throws IOException { + final var service = new Service( + Set.of(mockedMavenBuildResolver, mockedGradleBuildResolver), + mockedArtifactAvailableVersionsResolver + ); + final var fooIdentifier = new ArtifactIdentifier("foo-group-id", "foo-artifact-id"); + final var barIdentifier = new ArtifactIdentifier("bar-group-id", "bar-artifact-id"); + Mockito + .when( + mockedArtifactAvailableVersionsResolver.resolve( + Mockito.argThat( + artifact -> null != artifact && fooIdentifier.equals(artifact.getIdentifier()) + ), + Mockito.notNull() + ) + ) + .then( + invocation -> new ArtifactAvailableVersions( + invocation.getArgument(0), + List.of("1.0.0", "2.0.0") + ) + ); + Mockito + .when( + mockedArtifactAvailableVersionsResolver.resolve( + Mockito.argThat( + artifact -> null != artifact && barIdentifier.equals(artifact.getIdentifier()) + ), + Mockito.notNull() + ) + ) + .then( + invocation -> new ArtifactAvailableVersions( + invocation.getArgument(0), + List.of() + ) + ); + final var fooArtifact1 = new Artifact<>( + MavenArtifactType.DEPENDENCY, + fooIdentifier, + "1.0.0", + true + ); + final var fooArtifact2 = new Artifact<>( + MavenArtifactType.DEPENDENCY, + fooIdentifier, + "2.0.0-SNAPSHOT" + ); + final var fooArtifact3 = new Artifact<>( + MavenArtifactType.DEPENDENCY, + fooIdentifier, + "2.0.0" + ); + final var barArtifact = new Artifact<>( + MavenArtifactType.DEPENDENCY, + barIdentifier, + "1.1.0" + ); + final var build = new Build( + new BuildFile(BuildFileType.MAVEN, Path.of("src", "test", "resources", "pom.xml")), + List.of(), + List.of(fooArtifact1, fooArtifact2, fooArtifact3, barArtifact) + ); + assertThat(service.findArtifactUpdateVersions(build, false, false)).containsExactly( + new ArtifactUpdateVersion(fooArtifact1, "2.0.0"), + new ArtifactUpdateVersion(fooArtifact2, "2.0.0") + ); + assertThat(service.findArtifactUpdateVersions(build, true, false)).containsExactly( + new ArtifactUpdateVersion(fooArtifact2, "2.0.0") + ); + assertThat(service.findArtifactUpdateVersions(build, false, true)).containsExactly( + new ArtifactUpdateVersion(fooArtifact1, "2.0.0") + ); + } + + @Test + void testFindArtifactUpdateVersionsInvalid() throws IOException { + final var service = new Service(mockedMavenSession); + assertThatNullPointerException().isThrownBy( + () -> service.findArtifactUpdateVersions(null, false, false) + ); + } + @Test void testCreateUserArtifactFilter(@TempDir final Path tmpDirectory) throws IOException { final var ignoreFile = tmpDirectory.resolve(Path.of(".mvnchk-ignore"));