1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.buildcache.its;
20
21 import java.io.IOException;
22 import java.nio.charset.StandardCharsets;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.Arrays;
27 import java.util.function.Consumer;
28 import java.util.function.Predicate;
29 import java.util.regex.Pattern;
30 import java.util.stream.Collectors;
31 import java.util.stream.Stream;
32
33 import org.apache.maven.buildcache.its.junit.BeforeEach;
34 import org.apache.maven.buildcache.its.junit.Inject;
35 import org.apache.maven.buildcache.its.junit.IntegrationTest;
36 import org.apache.maven.buildcache.its.junit.IntegrationTestExtension;
37 import org.apache.maven.it.VerificationException;
38 import org.apache.maven.it.Verifier;
39 import org.jetbrains.annotations.NotNull;
40 import org.junit.jupiter.api.AfterEach;
41 import org.junit.jupiter.params.ParameterizedTest;
42 import org.junit.jupiter.params.provider.Arguments;
43 import org.junit.jupiter.params.provider.MethodSource;
44 import org.testcontainers.containers.GenericContainer;
45 import org.testcontainers.junit.jupiter.Container;
46 import org.testcontainers.junit.jupiter.Testcontainers;
47 import org.testcontainers.utility.DockerImageName;
48
49 import static org.apache.maven.buildcache.xml.CacheConfigImpl.REMOTE_SERVER_ID_PROPERTY_NAME;
50 import static org.apache.maven.buildcache.xml.CacheConfigImpl.REMOTE_URL_PROPERTY_NAME;
51 import static org.junit.jupiter.api.Assertions.assertFalse;
52 import static org.junit.jupiter.api.Assertions.assertTrue;
53
54 @IntegrationTest("src/test/projects/remote-cache-dav")
55 @Testcontainers(disabledWithoutDocker = true)
56 class RemoteCacheDavTest {
57
58 private static final String DAV_DOCKER_IMAGE =
59 "xama/nginx-webdav@sha256:84171a7e67d7e98eeaa67de58e3ce141ec1d0ee9c37004e7096698c8379fd9cf";
60 private static final String DAV_USERNAME = "admin";
61 private static final String DAV_PASSWORD = "admin";
62 private static final String REPO_ID = "build-cache";
63 private static final String HTTP_TRANSPORT_PRIORITY =
64 "aether.priority.org.eclipse.aether.transport.http.HttpTransporterFactory";
65 private static final String WAGON_TRANSPORT_PRIORITY =
66 "aether.priority.org.eclipse.aether.transport.wagon.WagonTransporterFactory";
67 private static final String MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED = "maven.build.cache.remote.save.enabled";
68
69 @Container
70 GenericContainer<?> dav;
71
72 @Inject
73 Verifier verifier;
74
75 Path basedir;
76 Path remoteCache;
77 Path localCache;
78 Path settings;
79 Path logDir;
80
81 @BeforeEach
82 void setup() throws IOException {
83 basedir = Paths.get(verifier.getBasedir());
84 remoteCache = basedir.resolveSibling("cache-remote").toAbsolutePath().normalize();
85 localCache = basedir.resolveSibling("cache-local").toAbsolutePath().normalize();
86 settings = basedir.resolve("../settings.xml").toAbsolutePath().normalize();
87 logDir = basedir.getParent();
88
89 Files.createDirectories(remoteCache);
90
91 Files.write(
92 settings,
93 ("<settings>"
94 + "<servers><server>"
95 + "<id>" + REPO_ID + "</id>"
96 + "<username>" + DAV_USERNAME + "</username>"
97 + "<password>" + DAV_PASSWORD + "</password>"
98 + "</server></servers></settings>")
99 .getBytes());
100
101 dav = new GenericContainer<>(DockerImageName.parse(DAV_DOCKER_IMAGE))
102 .withReuse(false)
103 .withExposedPorts(80)
104 .withEnv("WEBDAV_USERNAME", DAV_USERNAME)
105 .withEnv("WEBDAV_PASSWORD", DAV_PASSWORD)
106 .withFileSystemBind(remoteCache.toString(), "/var/webdav/public");
107 }
108
109 @AfterEach
110 void cleanup() throws Exception {
111 dav.execInContainer("rm", "-rf", "/var/webdav");
112 cleanDirs(localCache);
113 dav.close();
114 }
115
116 public static Stream<Arguments> transports() {
117 return Stream.of(Arguments.of("wagon"), Arguments.of("http"));
118 }
119
120 @ParameterizedTest
121 @MethodSource("transports")
122 void doTestRemoteCache(String transport) throws VerificationException, IOException {
123 String url =
124 ("wagon".equals(transport) ? "dav:" : "") + "http://localhost:" + dav.getFirstMappedPort() + "/mbce";
125 substitute(
126 basedir.resolve(".mvn/maven-build-cache-config.xml"),
127 "url",
128 url,
129 "id",
130 REPO_ID,
131 "location",
132 localCache.toString());
133
134 verifier.setAutoclean(false);
135
136 cleanDirs(localCache, remoteCache.resolve("mbce"));
137 assertFalse(hasBuildInfoXml(localCache), () -> error(localCache, "local", false));
138 assertFalse(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", false));
139
140 verifier.getCliOptions().clear();
141 verifier.addCliOption("--settings=" + settings);
142 verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
143 verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
144 verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=false");
145 verifier.setLogFileName("../log-1.txt");
146 verifier.executeGoals(Arrays.asList("clean", "install"));
147 verifier.verifyErrorFreeLog();
148
149 assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
150 assertFalse(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", false));
151
152 cleanDirs(localCache);
153
154 verifier.getCliOptions().clear();
155 verifier.addCliOption("--settings=" + settings);
156 if (!"wagon".equals(transport)) {
157 verifier.setSystemProperty("aether.connector.http.supportWebDav", "true");
158 }
159 verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
160 verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
161 verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=true");
162 verifier.setLogFileName("../log-2.txt");
163 verifier.executeGoals(Arrays.asList("clean", "install"));
164 verifier.verifyErrorFreeLog();
165
166 assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
167 assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
168
169 cleanDirs(localCache);
170
171 verifier.getCliOptions().clear();
172 verifier.addCliOption("--settings=" + settings);
173 if (!"wagon".equals(transport)) {
174 verifier.setSystemProperty("aether.connector.http.supportWebDav", "true");
175 }
176 verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
177 verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
178 verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=false");
179 verifier.setLogFileName("../log-3.txt");
180 verifier.executeGoals(Arrays.asList("clean", "install"));
181 verifier.verifyErrorFreeLog();
182
183 assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
184 assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
185
186
187 substitute(
188 basedir.resolve(".mvn/maven-build-cache-config.xml"),
189 "url",
190 "http://foo.com",
191 "id",
192 "foo",
193 "location",
194 localCache.toString());
195
196 cleanDirs(localCache);
197 try {
198
199
200 dav.execInContainer("rm", "-rf", "/var/webdav/public/*");
201 } catch (InterruptedException e) {
202 throw new IOException("cannot delete remote cache");
203 }
204
205 verifier.getCliOptions().clear();
206 verifier.addCliOption("--settings=" + settings);
207 verifier.addCliOption("-X");
208 verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
209 verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
210 verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=true");
211 verifier.setSystemProperty(REMOTE_URL_PROPERTY_NAME, url);
212 verifier.setSystemProperty(REMOTE_SERVER_ID_PROPERTY_NAME, REPO_ID);
213 verifier.setLogFileName("../log-4.txt");
214 verifier.executeGoals(Arrays.asList("clean", "install"));
215 verifier.verifyErrorFreeLog();
216
217 assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
218 assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
219 }
220
221 private boolean hasBuildInfoXml(Path cache) throws IOException {
222 return Files.walk(cache).anyMatch(isBuildInfoXml());
223 }
224
225 @NotNull
226 private Predicate<Path> isBuildInfoXml() {
227 return p -> p.getFileName().toString().equals("buildinfo.xml");
228 }
229
230 private void cleanDirs(Path... paths) throws IOException {
231 for (Path path : paths) {
232 IntegrationTestExtension.deleteDir(path);
233 Files.createDirectories(path);
234 Runtime.getRuntime().exec("chmod go+rwx " + path);
235 }
236 }
237
238 private static void substitute(Path path, String... strings) throws IOException {
239 String str = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
240 for (int i = 0; i < strings.length / 2; i++) {
241 str = str.replaceAll(Pattern.quote("${" + strings[i * 2] + "}"), strings[i * 2 + 1]);
242 }
243 Files.deleteIfExists(path);
244 Files.write(path, str.getBytes(StandardCharsets.UTF_8));
245 }
246
247 private String error(Path directory, String cache, boolean shouldHave) {
248 StringBuilder sb =
249 new StringBuilder("The " + cache + " cache should " + (shouldHave ? "" : "not ") + "contain a build\n");
250 try {
251 sb.append("Contents:\n");
252 Files.walk(directory).forEach(p -> sb.append(" ").append(p).append("\n"));
253
254 for (Path log : Files.list(logDir)
255 .filter(p -> p.getFileName().toString().matches("log.*\\.txt"))
256 .collect(Collectors.toList())) {
257 sb.append("Log file: ").append(log).append("\n");
258 Files.lines(log).forEach(l -> sb.append(" ").append(l).append("\n"));
259 }
260
261 sb.append("Container log:\n");
262 Stream.of(dav.getLogs().split("\n"))
263 .forEach(l -> sb.append(" ").append(l).append("\n"));
264
265 sb.append("Remote cache listing:\n");
266 ls(remoteCache, s -> sb.append(" ").append(s).append("\n"));
267 } catch (IOException e) {
268 sb.append("Error: ").append(e);
269 }
270 return sb.toString();
271 }
272
273 private static void ls(Path currentDir, Consumer<String> out) throws IOException {
274 Files.walk(currentDir)
275 .map(p -> new PathEntry(p, currentDir))
276 .sorted()
277 .map(PathEntry::longDisplay)
278 .forEach(out);
279 }
280 }