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 }