View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.buildcache.its;
20  
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.nio.file.Paths;
25  import java.nio.file.attribute.FileTime;
26  import java.time.Instant;
27  import java.util.Arrays;
28  
29  import org.apache.maven.buildcache.its.junit.IntegrationTest;
30  import org.apache.maven.it.VerificationException;
31  import org.apache.maven.it.Verifier;
32  import org.junit.jupiter.api.Test;
33  
34  import static org.junit.jupiter.api.Assertions.assertFalse;
35  import static org.junit.jupiter.api.Assertions.assertTrue;
36  
37  /**
38   * Tests that stale artifacts from source changes in multimodule projects are not cached.
39   * Verifies that the staging directory correctly preserves the full path structure including
40   * submodule paths relative to the multimodule root.
41   *
42   * <p>Scenario:
43   * <ol>
44   *   <li>Build multimodule project version A (creates module1/target/classes)</li>
45   *   <li>Simulate source change (source changes, target/classes remains stale)</li>
46   *   <li>Build without 'mvn clean' - should stage stale files with full path preservation</li>
47   *   <li>Verify staging directory structure: target/.maven-build-cache-stash/module1/target/classes</li>
48   * </ol>
49   */
50  @IntegrationTest("src/test/projects/stale-multimodule-artifact")
51  class StaleMultimoduleArtifactTest {
52  
53      @Test
54      void staleMultimoduleDirectoriesCorrectlyStaged(Verifier verifier) throws VerificationException, IOException {
55          verifier.setAutoclean(false);
56  
57          // Build version A: compile multimodule project
58          verifier.setLogFileName("../log-multimodule-version-a.txt");
59          verifier.executeGoals(Arrays.asList("clean", "compile"));
60          verifier.verifyErrorFreeLog();
61  
62          // Verify module1 class file was created
63          Path basedir = Paths.get(verifier.getBasedir());
64          Path module1ClassesDir = basedir.resolve("module1/target/classes");
65          Path module1Class = module1ClassesDir.resolve("org/example/Module1.class");
66          assertTrue(Files.exists(module1Class), "Module1.class should exist after compile");
67  
68          // Simulate source change (e.g., branch switch, external update) by:
69          // 1. Modifying source file (simulates different source version)
70          // 2. Making class file appear OLDER than build start time (stale)
71          Path sourceFile = basedir.resolve("module1/src/main/java/org/example/Module1.java");
72          String content = new String(Files.readAllBytes(sourceFile), "UTF-8");
73          Files.write(sourceFile, content.replace("Version A", "Version B").getBytes("UTF-8"));
74  
75          // Backdate the class file to simulate stale artifact from previous build
76          FileTime oldTime = FileTime.from(Instant.now().minusSeconds(3600)); // 1 hour ago
77          Files.setLastModifiedTime(module1Class, oldTime);
78  
79          // Build without clean (simulates developer workflow)
80          // The staleness detection should:
81          // 1. Move module1/target/classes to target/.maven-build-cache-stash/module1/target/classes
82          // 2. Force recompilation (Maven sees clean module1/target/)
83          // 3. After save(), restore or discard based on whether files were rebuilt
84          verifier.setLogFileName("../log-multimodule-version-b.txt");
85          verifier.executeGoals(Arrays.asList("compile"));
86          verifier.verifyErrorFreeLog();
87  
88          // Verify that compiler detected source change and recompiled
89          // (class file should have new timestamp after recompile)
90          FileTime newTime = Files.getLastModifiedTime(module1Class);
91          assertTrue(
92                  newTime.toMillis() > oldTime.toMillis(),
93                  "Compiler should have recompiled stale class (new timestamp: " + newTime + ", old timestamp: " + oldTime
94                          + ")");
95  
96          // Verify that staging directory was cleaned up after restore.
97          // After a successful build, all files should be either:
98          // 1. Restored (moved back to original location) - for unchanged files
99          // 2. Discarded (deleted from staging) - for rebuilt files
100         // So the staging directory should be empty or deleted
101         Path stagingDir = basedir.resolve("target/maven-build-cache-extension");
102         assertFalse(
103                 Files.exists(stagingDir),
104                 "Staging directory should be deleted after all files are restored or discarded");
105     }
106 }