gnodet commented on code in PR #2198:
URL: https://github.com/apache/maven-resolver/pull/2198#discussion_r4238567211


##########
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java:
##########
@@ -377,4 +379,47 @@ void testSetArtifactTimestamps() throws 
InstallationException {
                 localArtifactFile.lastModified(),
                 "artifact timestamp was not set to src file");
     }
+
+    @Test
+    void testHardLinkInstallCreatesHardLink() throws InstallationException, 
IOException {
+        // default: hardLink=true — installer attempts a hard link, falls back 
to copy on cross-device
+        request.addArtifact(artifact);
+        installer.install(session, request);
+
+        assertTrue(localArtifactFile.exists());
+        assertEquals("artifact", TestFileUtils.readString(localArtifactFile));
+
+        // On same-device filesystems, verify a hard link was created (same 
inode).
+        // On cross-device (common in CI containers where /tmp and project dir 
are on different mounts),
+        // Files.createLink throws IOException and the installer falls back to 
copy — both are correct.
+        Path srcPath = artifact.getFile().toPath();
+        Path dstPath = localArtifactFile.toPath();
+        try {
+            boolean sameDevice =
+                    Files.getAttribute(srcPath, 
"unix:dev").equals(Files.getAttribute(dstPath, "unix:dev"));
+            if (sameDevice) {
+                // hard link must have been created — same inode expected
+                assertEquals(
+                        Files.getAttribute(srcPath, "unix:ino"),
+                        Files.getAttribute(dstPath, "unix:ino"),
+                        "installed artifact should be a hard link to the 
source on same-device FS");
+            }
+            // cross-device: fallback to copy is correct — content already 
asserted above
+        } catch (UnsupportedOperationException | IllegalArgumentException 
ignored) {

Review Comment:
   ⚠️ **Test degeneracy in CI (re-raised from prior review — still open):** The 
`catch (UnsupportedOperationException | IllegalArgumentException ignored)` 
block silently swallows the inode check on non-POSIX or cross-device 
filesystems, which covers virtually every CI environment (Docker, GitHub 
Actions). On those systems, this test reduces to the same content-equality 
assertion as every other install test — it provides zero behavioral coverage of 
the hard-link path.
   
   The fix requires either:
   1. Use a `@TempDir` on the same filesystem as the local repo, forcing 
same-device layout, then unconditionally assert inode equality.
   2. Or subclass `DefaultInstaller` in the test to override the 
`Files.createLink` call with a counter/spy, then assert the counter was 
incremented.



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java:
##########
@@ -205,7 +214,22 @@ private void install(RepositorySystemSession session, 
RequestTrace trace, Artifa
                 throw new IllegalStateException("cannot install " + dstPath + 
" to same path");
             }
 
-            pathProcessor.copyWithTimestamp(srcPath, dstPath);
+            if (hardLink) {
+                Files.createDirectories(dstPath.getParent());
+                if (Files.isDirectory(dstPath)) {
+                    throw new IOException("destination is a directory: " + 
dstPath);
+                }
+                try {
+                    Files.deleteIfExists(dstPath);
+                    Files.createLink(dstPath, srcPath);
+                } catch (UnsupportedOperationException | 
IllegalArgumentException | IOException e) {
+                    // cross-device link (EXDEV), provider mismatch (e.g. 
Jimfs), or unsupported FS — fall back to copy
+                    logger.warn("Hard link not supported for {}, falling back 
to copy: {}", dstPath, e.getMessage());

Review Comment:
   💡 **Per-artifact WARN spam on cross-device environments:** `logger.warn` is 
called inside the per-artifact loop — in a multi-module build where `~/.m2` and 
the project are on different mounts (common in Docker containers), every 
artifact install produces a separate WARN line. A 50-module build emits 50+ 
warnings saying the same thing.
   
   Consider logging the first fallback at WARN and subsequent ones at DEBUG 
within the same install request. One approach: pass a `boolean[] warnedOnce = 
{false}` array into the private method, or check a local flag in the outer loop:
   
   ```suggestion
                       logger.debug("Hard link not supported for {}, falling 
back to copy: {}", dstPath, e.getMessage());
                       pathProcessor.copyWithTimestamp(srcPath, dstPath);
   ```
   
   Or alternatively, cache the fallback decision per session and only warn on 
first encounter.



##########
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java:
##########
@@ -377,4 +379,47 @@ void testSetArtifactTimestamps() throws 
InstallationException {
                 localArtifactFile.lastModified(),
                 "artifact timestamp was not set to src file");
     }
+
+    @Test
+    void testHardLinkInstallCreatesHardLink() throws InstallationException, 
IOException {
+        // default: hardLink=true — installer attempts a hard link, falls back 
to copy on cross-device
+        request.addArtifact(artifact);
+        installer.install(session, request);
+
+        assertTrue(localArtifactFile.exists());
+        assertEquals("artifact", TestFileUtils.readString(localArtifactFile));
+
+        // On same-device filesystems, verify a hard link was created (same 
inode).
+        // On cross-device (common in CI containers where /tmp and project dir 
are on different mounts),
+        // Files.createLink throws IOException and the installer falls back to 
copy — both are correct.
+        Path srcPath = artifact.getFile().toPath();
+        Path dstPath = localArtifactFile.toPath();
+        try {
+            boolean sameDevice =
+                    Files.getAttribute(srcPath, 
"unix:dev").equals(Files.getAttribute(dstPath, "unix:dev"));
+            if (sameDevice) {
+                // hard link must have been created — same inode expected
+                assertEquals(
+                        Files.getAttribute(srcPath, "unix:ino"),
+                        Files.getAttribute(dstPath, "unix:ino"),
+                        "installed artifact should be a hard link to the 
source on same-device FS");
+            }
+            // cross-device: fallback to copy is correct — content already 
asserted above
+        } catch (UnsupportedOperationException | IllegalArgumentException 
ignored) {
+            // non-POSIX filesystem — inode/device check not available; 
content assertion is sufficient
+        }
+    }
+
+    @Test
+    void testHardLinkDisabledFallsThroughToCopy() throws 
InstallationException, IOException {
+        // set hardLink=false — installer must copy, not hard-link
+        session.setConfigProperty(ConfigurationProperties.INSTALLER_HARD_LINK, 
false);
+        request.addArtifact(artifact);
+        installer.install(session, request);
+
+        assertTrue(localArtifactFile.exists());

Review Comment:
   ⚠️ **Test behavioral gap (re-raised from prior review — still open):** 
`testHardLinkDisabledFallsThroughToCopy` only asserts that the installed file 
exists and has the right content — which is identical to the happy path of any 
other install test. It does not verify that `Files.createLink` was NOT called, 
so this test would pass even if the `hardLink=false` branch were never taken.
   
   To actually test the `hardLink=false` code path, inject a spy on 
`PathProcessor` and assert `copyWithTimestamp` was called, or check that the 
installed file has a different inode from the source on same-device filesystems.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to