RepositoryLeakageTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.project;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.apache.maven.artifact.repository.ArtifactRepository;
import org.codehaus.plexus.testing.PlexusTest;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Test to verify that repositories from one project don't leak to sibling projects.
 */
@PlexusTest
public class RepositoryLeakageTest extends AbstractMavenProjectTestCase {

    @Test
    @SuppressWarnings("checkstyle:MethodLength")
    public void testRepositoryLeakageBetweenSiblings() throws Exception {
        // Create a temporary directory structure for our test
        Path tempDir = Files.createTempDirectory("maven-repo-leakage-test");

        try {
            // Create parent POM
            Path parentPom = tempDir.resolve("pom.xml");
            Files.writeString(
                    parentPom,
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
                    <modelVersion>4.0.0</modelVersion>
                    <groupId>test</groupId>
                    <artifactId>parent</artifactId>
                    <version>1.0</version>
                    <packaging>pom</packaging>

                    <modules>
                        <module>child1</module>
                        <module>child2</module>
                    </modules>
                </project>
                """);

            // Create child1 with specific repository
            Path child1Dir = tempDir.resolve("child1");
            Files.createDirectories(child1Dir);
            Path child1Pom = child1Dir.resolve("pom.xml");
            Files.writeString(
                    child1Pom,
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
                    <modelVersion>4.0.0</modelVersion>
                    <parent>
                        <groupId>test</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0</version>
                    </parent>
                    <artifactId>child1</artifactId>

                    <repositories>
                        <repository>
                            <id>child1-repo</id>
                            <url>https://child1.example.com/repo</url>
                        </repository>
                    </repositories>
                </project>
                """);

            // Create child2 with different repository
            Path child2Dir = tempDir.resolve("child2");
            Files.createDirectories(child2Dir);
            Path child2Pom = child2Dir.resolve("pom.xml");
            Files.writeString(
                    child2Pom,
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
                    <modelVersion>4.0.0</modelVersion>
                    <parent>
                        <groupId>test</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0</version>
                    </parent>
                    <artifactId>child2</artifactId>

                    <repositories>
                        <repository>
                            <id>child2-repo</id>
                            <url>https://child2.example.com/repo</url>
                        </repository>
                    </repositories>
                </project>
                """);

            // Create a shared ProjectBuildingRequest
            ProjectBuildingRequest sharedRequest = newBuildingRequest();

            // Build child1 first
            ProjectBuildingResult result1 = projectBuilder.build(child1Pom.toFile(), sharedRequest);
            MavenProject child1Project = result1.getProject();

            // Capture repositories after building child1

            // Build child2 using the same shared request
            ProjectBuildingResult result2 = projectBuilder.build(child2Pom.toFile(), sharedRequest);
            MavenProject child2Project = result2.getProject();

            // Capture repositories after building child2
            List<ArtifactRepository> repositoriesAfterChild2 = List.copyOf(sharedRequest.getRemoteRepositories());

            // Verify that child1 has its own repository
            boolean child1HasOwnRepo = child1Project.getRemoteArtifactRepositories().stream()
                    .anyMatch(repo -> "child1-repo".equals(repo.getId()));
            assertTrue(child1HasOwnRepo, "Child1 should have its own repository");

            // Verify that child2 has its own repository
            boolean child2HasOwnRepo = child2Project.getRemoteArtifactRepositories().stream()
                    .anyMatch(repo -> "child2-repo".equals(repo.getId()));
            assertTrue(child2HasOwnRepo, "Child2 should have its own repository");

            // Print debug information
            System.out.println("=== REPOSITORY LEAKAGE TEST RESULTS ===");
            System.out.println(
                    "Repositories in shared request after building child2: " + repositoriesAfterChild2.size());
            repositoriesAfterChild2.forEach(
                    repo -> System.out.println("  - " + repo.getId() + " (" + repo.getUrl() + ")"));

            System.out.println("Child1 project repositories:");
            child1Project
                    .getRemoteArtifactRepositories()
                    .forEach(repo -> System.out.println("  - " + repo.getId() + " (" + repo.getUrl() + ")"));

            System.out.println("Child2 project repositories:");
            child2Project
                    .getRemoteArtifactRepositories()
                    .forEach(repo -> System.out.println("  - " + repo.getId() + " (" + repo.getUrl() + ")"));
            System.out.println("=======================================");

            // Check for leakage: child2 should NOT have child1's repository
            boolean child2HasChild1Repo = child2Project.getRemoteArtifactRepositories().stream()
                    .anyMatch(repo -> "child1-repo".equals(repo.getId()));
            assertFalse(child2HasChild1Repo, "Child2 should NOT have child1's repository (leakage detected!)");

            // Check for leakage in the shared request
            boolean sharedRequestHasChild1Repo =
                    repositoriesAfterChild2.stream().anyMatch(repo -> "child1-repo".equals(repo.getId()));
            boolean sharedRequestHasChild2Repo =
                    repositoriesAfterChild2.stream().anyMatch(repo -> "child2-repo".equals(repo.getId()));

            // Print debug information
            /*
            System.out.println("Repositories after child1: " + repositoriesAfterChild1.size());
            repositoriesAfterChild1.forEach(repo -> System.out.println("  - " + repo.getId() + ": " + repo.getUrl()));

            System.out.println("Repositories after child2: " + repositoriesAfterChild2.size());
            repositoriesAfterChild2.forEach(repo -> System.out.println("  - " + repo.getId() + ": " + repo.getUrl()));
            */

            // The shared request should not accumulate repositories from both children
            if (sharedRequestHasChild1Repo && sharedRequestHasChild2Repo) {
                fail("REPOSITORY LEAKAGE DETECTED: Shared request contains repositories from both children!");
            }

        } finally {
            // Clean up
            deleteRecursively(tempDir.toFile());
        }
    }

    private void deleteRecursively(File file) {
        if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    deleteRecursively(child);
                }
            }
        }
        file.delete();
    }
}