1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.micronaut.build.testresources;
17
18 import io.micronaut.build.services.DependencyResolutionService;
19 import io.micronaut.testresources.buildtools.*;
20 import org.apache.maven.execution.MavenSession;
21 import org.apache.maven.model.Dependency;
22 import org.apache.maven.plugin.MojoExecutionException;
23 import org.apache.maven.plugin.logging.Log;
24 import org.apache.maven.plugin.logging.SystemStreamLog;
25 import org.apache.maven.project.MavenProject;
26 import org.apache.maven.toolchain.ToolchainManager;
27 import org.eclipse.aether.artifact.Artifact;
28 import org.eclipse.aether.resolution.DependencyResolutionException;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.nio.file.*;
33 import java.nio.file.attribute.BasicFileAttributes;
34 import java.time.Duration;
35 import java.util.*;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40
41 import static io.micronaut.build.MojoUtils.findJavaExecutable;
42 import static io.micronaut.build.services.DependencyResolutionService.testResourcesModuleToAetherArtifact;
43 import static io.micronaut.build.services.DependencyResolutionService.toClasspathFiles;
44 import static java.util.stream.Stream.concat;
45
46
47
48
49 public class TestResourcesHelper {
50
51 private static final String TEST_RESOURCES_PROPERTIES = "test-resources.properties";
52 private static final String PORT_FILE_NAME = "test-resources-port.txt";
53
54 private final boolean enabled;
55
56 private final boolean keepAlive;
57
58 private final boolean shared;
59
60 private final File buildDirectory;
61
62 private final Log log;
63
64 private Integer explicitPort;
65
66 private Integer clientTimeout;
67
68 private MavenProject mavenProject;
69
70 private MavenSession mavenSession;
71
72 private DependencyResolutionService dependencyResolutionService;
73
74 private ToolchainManager toolchainManager;
75
76 private String testResourcesVersion;
77
78 private boolean classpathInference;
79
80 private List<Dependency> testResourcesDependencies;
81
82 private String sharedServerNamespace;
83
84 public TestResourcesHelper(boolean enabled, boolean keepAlive, boolean shared, File buildDirectory,
85 Integer explicitPort, Integer clientTimeout, MavenProject mavenProject,
86 MavenSession mavenSession, DependencyResolutionService dependencyResolutionService,
87 ToolchainManager toolchainManager, String testResourcesVersion,
88 boolean classpathInference, List<Dependency> testResourcesDependencies,
89 String sharedServerNamespace) {
90 this(enabled, keepAlive, shared, buildDirectory);
91 this.explicitPort = explicitPort;
92 this.clientTimeout = clientTimeout;
93 this.mavenProject = mavenProject;
94 this.mavenSession = mavenSession;
95 this.dependencyResolutionService = dependencyResolutionService;
96 this.toolchainManager = toolchainManager;
97 this.testResourcesVersion = testResourcesVersion;
98 this.classpathInference = classpathInference;
99 this.testResourcesDependencies = testResourcesDependencies;
100 this.sharedServerNamespace = sharedServerNamespace;
101 }
102
103 public TestResourcesHelper(boolean enabled, boolean keepAlive, boolean shared, File buildDirectory) {
104 this.enabled = enabled;
105 this.keepAlive = keepAlive;
106 this.shared = shared;
107 this.buildDirectory = buildDirectory;
108 this.log = new SystemStreamLog();
109 }
110
111
112
113
114 public void start() throws MojoExecutionException {
115 if (!enabled) {
116 return;
117 }
118 try {
119 doStart();
120 } catch (Exception e) {
121 throw new MojoExecutionException("Unable to start test resources server", e);
122 }
123 }
124
125 private void doStart() throws DependencyResolutionException, IOException {
126 if (shared) {
127 if (sharedServerNamespace != null) {
128 log.info("Test Resources is configured in shared mode with the namespace: " + sharedServerNamespace);
129 } else {
130 log.info("Test Resources is configured in shared mode");
131 }
132 }
133 String accessToken = UUID.randomUUID().toString();
134 Path buildDir = buildDirectory.toPath();
135 Path serverSettingsDirectory = getServerSettingsDirectory();
136 AtomicBoolean serverStarted = new AtomicBoolean(false);
137 ServerUtils.startOrConnectToExistingServer(
138 explicitPort,
139 buildDir.resolve(PORT_FILE_NAME),
140 serverSettingsDirectory,
141 accessToken,
142 resolveServerClasspath(),
143 clientTimeout,
144 new ServerFactory() {
145 Process process;
146
147 @Override
148 public void startServer(ServerUtils.ProcessParameters processParameters) throws IOException {
149 log.info("Starting Micronaut Test Resources service");
150 String javaBin = findJavaExecutable(toolchainManager, mavenSession);
151 List<String> cli = new ArrayList<>();
152 cli.add(javaBin);
153 cli.addAll(processParameters.getJvmArguments());
154 processParameters.getSystemProperties().forEach((key, value) -> cli.add("-D" + key + "=" + value));
155 cli.add("-cp");
156 cli.add(processParameters.getClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)));
157 cli.add(processParameters.getMainClass());
158 cli.addAll(processParameters.getArguments());
159 ProcessBuilder builder = new ProcessBuilder(cli);
160 process = builder.inheritIO().start();
161 serverStarted.set(true);
162 }
163
164 @Override
165 public void waitFor(Duration duration) throws InterruptedException {
166 if (process != null) {
167 process.waitFor(duration.toMillis(), TimeUnit.MILLISECONDS);
168 }
169 }
170 }
171 );
172 if (keepAlive) {
173 log.info("Micronaut Test Resources service is started in the background. To stop it, run the following command: 'mvn mn:" + StopTestResourcesServerMojo.NAME + "'");
174 }
175 if (!serverStarted.get()) {
176
177
178
179 Path keepalive = getKeepAliveFile();
180 Files.write(keepalive, "true".getBytes());
181 }
182
183
184 copyServerSettingsToClasspath(buildDir, serverSettingsDirectory);
185 }
186
187 private void copyServerSettingsToClasspath(Path buildDir, Path serverSettingsDirectory) throws IOException {
188 Path testClassesDir = buildDir.resolve("test-classes");
189 if (!Files.exists(testClassesDir)) {
190 Files.createDirectories(testClassesDir);
191 }
192 Files.copy(serverSettingsDirectory.resolve(TEST_RESOURCES_PROPERTIES), testClassesDir.resolve(TEST_RESOURCES_PROPERTIES), StandardCopyOption.REPLACE_EXISTING);
193 }
194
195 private List<File> resolveServerClasspath() throws DependencyResolutionException {
196 Stream<Artifact> clientDependencies = Stream.of(testResourcesModuleToAetherArtifact("client", testResourcesVersion));
197
198 List<MavenDependency> applicationDependencies = Collections.emptyList();
199 if (classpathInference) {
200 applicationDependencies = getApplicationDependencies();
201 }
202 Stream<Artifact> serverDependencies =
203 TestResourcesClasspath.inferTestResourcesClasspath(applicationDependencies, testResourcesVersion)
204 .stream()
205 .map(DependencyResolutionService::testResourcesDependencyToAetherArtifact);
206
207 List<org.apache.maven.model.Dependency> extraDependencies =
208 testResourcesDependencies != null ? testResourcesDependencies : Collections.emptyList();
209
210 Stream<Artifact> extraDependenciesStream = extraDependencies.stream()
211 .map(DependencyResolutionService::mavenDependencyToAetherArtifact);
212
213 Stream<Artifact> artifacts = concat(concat(clientDependencies, serverDependencies), extraDependenciesStream);
214
215 return toClasspathFiles(dependencyResolutionService.artifactResultsFor(artifacts, true));
216 }
217
218 private List<MavenDependency> getApplicationDependencies() {
219 return this.mavenProject.getDependencies().stream()
220 .map(DependencyResolutionService::mavenDependencyToTestResourcesDependency)
221 .collect(Collectors.toList());
222 }
223
224
225
226
227 public void stop() throws MojoExecutionException {
228 if (!enabled || Boolean.TRUE.equals(keepAlive)) {
229 return;
230 }
231 if (Files.exists(getKeepAliveFile())) {
232 try {
233 Files.delete(getKeepAliveFile());
234 } catch (IOException e) {
235 throw new MojoExecutionException("Failed to delete keepalive file", e);
236 }
237 return;
238 }
239 try {
240 Optional<ServerSettings> optionalServerSettings = ServerUtils.readServerSettings(getServerSettingsDirectory());
241 if (optionalServerSettings.isPresent() && ServerUtils.isServerStarted(optionalServerSettings.get().getPort())) {
242 log.info("Shutting down Micronaut Test Resources service");
243 doStop();
244 }
245 } catch (Exception e) {
246 throw new MojoExecutionException("Unable to stop test resources server", e);
247 }
248 }
249
250 private void doStop() throws IOException {
251 Path settingsDirectory = getServerSettingsDirectory();
252 ServerUtils.stopServer(settingsDirectory);
253 Files.walkFileTree(settingsDirectory, new SimpleFileVisitor<Path>() {
254 @Override
255 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
256 Files.delete(file);
257 return super.visitFile(file, attrs);
258 }
259
260 @Override
261 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
262 Files.delete(dir);
263 return super.postVisitDirectory(dir, exc);
264 }
265 });
266 }
267
268 private Path getServerSettingsDirectory() {
269 if (shared) {
270 return ServerUtils.getDefaultSharedSettingsPath(sharedServerNamespace);
271 }
272 return serverSettingsDirectoryOf(buildDirectory.toPath());
273 }
274
275 private Path getKeepAliveFile() {
276 return getServerSettingsDirectory().resolve("keepalive");
277 }
278
279 private Path serverSettingsDirectoryOf(Path buildDir) {
280 return buildDir.resolve("../.micronaut/test-resources");
281 }
282
283
284
285
286 public void setSharedServerNamespace(String sharedServerNamespace) {
287 this.sharedServerNamespace = sharedServerNamespace;
288 }
289 }