properties = new HashMap<>();
62 | for (String key : p.stringPropertyNames()) {
63 | properties.put(key, p.getProperty(key));
64 | }
65 | return Collections.unmodifiableMap(properties);
66 | }
67 |
68 | public String get(String key) {
69 | return properties.get(key);
70 | }
71 |
72 | public File getUserSettings() {
73 | // can be null
74 | String path = properties.get(PROP_USER_SETTING_FILE);
75 | if (path == null) {
76 | path = properties.get(PROP_USER_SETTING);
77 | }
78 | if (path == null) {
79 | return null;
80 | }
81 | File file = new File(path);
82 | Assert.assertTrue("Can read user settings.xml", file.canRead());
83 | return file;
84 | }
85 |
86 | public File getGlobalSettings() {
87 | // can be null
88 | String path = properties.get(PROP_GLOBAL_SETTING_FILE);
89 | if (path == null) {
90 | return null;
91 | }
92 | File file = new File(path);
93 | Assert.assertTrue("Can read global settings.xml", file.canRead());
94 | return file;
95 | }
96 |
97 | public File getLocalRepository() {
98 | String path = properties.get(PROP_LOCAL_REPOSITORY);
99 | Assert.assertNotNull("Local repository specified", path);
100 | return new File(path);
101 | }
102 |
103 | public boolean getOffline() {
104 | String value = properties.get(PROP_OFFLINE);
105 | return value != null ? Boolean.parseBoolean(value) : false;
106 | }
107 |
108 | public boolean getUpdateSnapshots() {
109 | String value = properties.get(PROP_UPDATESNAPSHOTS);
110 | return value != null ? Boolean.parseBoolean(value) : false;
111 | }
112 |
113 | public String getPluginVersion() {
114 | return properties.get("project.version");
115 | }
116 |
117 | /**
118 | * Returns location of the current project classes, i.e. target/classes directory, and all project dependencies with scope=runtime.
119 | *
120 | * Useful for testing maven core extensions, {@link MavenRuntimeBuilder#withExtensions(java.util.Collection)}
121 | */
122 | public List getRuntimeClasspath() {
123 | StringTokenizer st = new StringTokenizer(properties.get(PROP_CLASSPATH), File.pathSeparator);
124 | List dependencies = new ArrayList<>();
125 | while (st.hasMoreTokens()) {
126 | dependencies.add(new File(st.nextToken()));
127 | }
128 | return dependencies;
129 | }
130 |
131 | public List getRepositories() {
132 | TreeMap repositories = new TreeMap<>();
133 | for (Map.Entry property : properties.entrySet()) {
134 | if (property.getKey().startsWith(PROP_REPOSITORY)) {
135 | repositories.put(property.getKey(), property.getValue());
136 | }
137 | }
138 | return new ArrayList<>(repositories.values());
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/TestResources.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing;
9 |
10 | import org.junit.Rule;
11 | import org.junit.rules.TestRule;
12 | import org.junit.runner.Description;
13 | import org.junit.runners.model.Statement;
14 |
15 | /**
16 | * Junit4 test {@link Rule} to extract and assert test resources.
17 | */
18 | public class TestResources extends AbstractTestResources implements TestRule {
19 |
20 | public TestResources() {
21 | super();
22 | }
23 |
24 | public TestResources(String projectsDir, String workDir) {
25 | super(projectsDir, workDir);
26 | }
27 |
28 | @Override
29 | public Statement apply(Statement base, Description d) {
30 | return new Statement() {
31 | @Override
32 | public void evaluate() throws Throwable {
33 | starting(d.getTestClass(), d.getMethodName());
34 | base.evaluate();
35 | }
36 | };
37 | }
38 |
39 | @Override
40 | String getRequiredAnnotationClassName() {
41 | return "org.junit.Rule";
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/TestResources5.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing;
9 |
10 | import java.lang.reflect.Method;
11 | import org.junit.jupiter.api.extension.BeforeEachCallback;
12 | import org.junit.jupiter.api.extension.ExtensionContext;
13 |
14 | /**
15 | * JUnit 5 extension to extract and assert test resources.
16 | */
17 | public class TestResources5 extends AbstractTestResources implements BeforeEachCallback {
18 |
19 | public TestResources5() {
20 | super();
21 | }
22 |
23 | public TestResources5(String projectsDir, String workDir) {
24 | super(projectsDir, workDir);
25 | }
26 |
27 | @Override
28 | public void beforeEach(ExtensionContext context) throws Exception {
29 | String methodName = context.getTestMethod().map(Method::getName).orElse(null);
30 | starting(context.getRequiredTestClass(), methodName);
31 | }
32 |
33 | @Override
34 | String getRequiredAnnotationClassName() {
35 | return "org.junit.jupiter.api.extension.RegisterExtension";
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/ForkedLauncher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | /*
11 | * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
12 | * agreements. See the NOTICE file distributed with this work for additional information regarding
13 | * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
14 | * "License"); you may not use this file except in compliance with the License. You may obtain a
15 | * copy of the License at
16 | *
17 | * http://www.apache.org/licenses/LICENSE-2.0
18 | *
19 | * Unless required by applicable law or agreed to in writing, software distributed under the License
20 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21 | * or implied. See the License for the specific language governing permissions and limitations under
22 | * the License.
23 | */
24 |
25 | import static io.takari.maven.testing.executor.MavenInstallationUtils.MAVEN4_MAIN_CLASS;
26 | import static io.takari.maven.testing.executor.MavenInstallationUtils.SYSPROP_MAVEN_MAIN_CLASS;
27 |
28 | import java.io.File;
29 | import java.io.FileOutputStream;
30 | import java.io.IOException;
31 | import java.io.OutputStream;
32 | import java.io.PrintStream;
33 | import java.nio.charset.Charset;
34 | import java.nio.file.Files;
35 | import java.util.Collections;
36 | import java.util.HashMap;
37 | import java.util.Iterator;
38 | import java.util.List;
39 | import java.util.Map;
40 | import java.util.regex.Matcher;
41 | import java.util.regex.Pattern;
42 | import org.apache.commons.exec.CommandLine;
43 | import org.apache.commons.exec.DefaultExecutor;
44 | import org.apache.commons.exec.ExecuteException;
45 | import org.apache.commons.exec.PumpStreamHandler;
46 | import org.apache.commons.exec.ShutdownHookProcessDestroyer;
47 | import org.codehaus.plexus.util.Os;
48 |
49 | /**
50 | * @author Benjamin Bentmann
51 | */
52 | class ForkedLauncher implements MavenLauncher {
53 |
54 | private final File mavenHome;
55 |
56 | private final File classworldsJar;
57 |
58 | private final Map envVars;
59 |
60 | private final List extensions;
61 |
62 | private final List args;
63 |
64 | private final List jvmArgs;
65 |
66 | public ForkedLauncher(
67 | File mavenHome,
68 | File classworldsConf,
69 | List extensions,
70 | Map envVars,
71 | List args,
72 | List jvmArgs) {
73 | this.args = args;
74 | this.jvmArgs = jvmArgs;
75 | if (mavenHome == null) {
76 | throw new NullPointerException();
77 | }
78 | if (classworldsConf != null) {
79 | throw new IllegalArgumentException("Custom classworlds configuration file is not supported");
80 | }
81 |
82 | this.mavenHome = mavenHome;
83 | this.envVars = envVars;
84 | this.extensions = extensions;
85 |
86 | File classworldsJar = null;
87 | File[] files = new File(mavenHome, "boot").listFiles();
88 | if (files != null) {
89 | for (File file : files) {
90 | String name = file.getName();
91 | if (name.startsWith("plexus-classworlds-") && name.endsWith(".jar")) {
92 | classworldsJar = file;
93 | break;
94 | }
95 | }
96 | }
97 | if (classworldsJar == null) {
98 | throw new IllegalArgumentException("Invalid maven home " + mavenHome);
99 | }
100 | this.classworldsJar = classworldsJar;
101 | }
102 |
103 | public int run(
104 | String[] cliArgs,
105 | Map envVars,
106 | File multiModuleProjectDirectory,
107 | File workingDirectory,
108 | File logFile)
109 | throws IOException, LauncherException {
110 | String javaHome;
111 | if (envVars == null || envVars.get("JAVA_HOME") == null) {
112 | javaHome = System.getProperty("java.home");
113 | } else {
114 | javaHome = envVars.get("JAVA_HOME");
115 | }
116 |
117 | File executable = new File(javaHome, Os.isFamily(Os.FAMILY_WINDOWS) ? "bin/javaw.exe" : "bin/java");
118 |
119 | CommandLine cli = new CommandLine(executable);
120 | cli.addArgument("-classpath").addArgument(classworldsJar.getAbsolutePath());
121 | cli.addArgument("-Dclassworlds.conf=" + new File(mavenHome, "bin/m2.conf").getAbsolutePath());
122 | cli.addArgument("-Dmaven.home=" + mavenHome.getAbsolutePath());
123 | cli.addArgument("-Dmaven.multiModuleProjectDirectory=" + multiModuleProjectDirectory.getAbsolutePath());
124 | cli.addArgument("-D" + SYSPROP_MAVEN_MAIN_CLASS + "=" + MAVEN4_MAIN_CLASS);
125 |
126 | cli.addArguments(jvmArgs.toArray(new String[0]));
127 |
128 | cli.addArgument("org.codehaus.plexus.classworlds.launcher.Launcher");
129 |
130 | cli.addArguments(args.toArray(new String[0]));
131 | if (extensions != null && !extensions.isEmpty()) {
132 | cli.addArgument("-Dmaven.ext.class.path=" + toPath(extensions));
133 | }
134 |
135 | cli.addArguments(cliArgs);
136 |
137 | Map env = new HashMap<>();
138 | env.put("M2_HOME", mavenHome.getAbsolutePath());
139 | if (envVars != null) {
140 | env.putAll(envVars);
141 | }
142 | if (envVars == null || envVars.get("JAVA_HOME") == null) {
143 | env.put("JAVA_HOME", System.getProperty("java.home"));
144 | }
145 |
146 | DefaultExecutor executor = DefaultExecutor.builder()
147 | .setWorkingDirectory(workingDirectory.getAbsoluteFile())
148 | .get();
149 | executor.setProcessDestroyer(new ShutdownHookProcessDestroyer());
150 |
151 | try (OutputStream log = new FileOutputStream(logFile)) {
152 | PrintStream out = new PrintStream(log);
153 | out.format("Maven Executor implementation: %s\n", getClass().getName());
154 | out.format("Maven home: %s\n", mavenHome);
155 | out.format("Build work directory: %s\n", workingDirectory);
156 | out.format("Environment: %s\n", env);
157 | out.format("Command line: %s\n\n", cli.toString());
158 | out.flush();
159 |
160 | PumpStreamHandler streamHandler = new PumpStreamHandler(log);
161 | executor.setStreamHandler(streamHandler);
162 | return executor.execute(cli, env); // this throws ExecuteException if process return code != 0
163 | } catch (ExecuteException e) {
164 | throw new LauncherException("Failed to run Maven: " + e.getMessage() + "\n" + cli, e);
165 | }
166 | }
167 |
168 | private static String toPath(List strings) {
169 | StringBuilder sb = new StringBuilder();
170 | for (String string : strings) {
171 | if (sb.length() > 0) {
172 | sb.append(File.pathSeparatorChar);
173 | }
174 | sb.append(string);
175 | }
176 | return sb.toString();
177 | }
178 |
179 | @Override
180 | public int run(String[] cliArgs, File multiModuleProjectDirectory, File workingDirectory, File logFile)
181 | throws IOException, LauncherException {
182 | return run(cliArgs, envVars, multiModuleProjectDirectory, workingDirectory, logFile);
183 | }
184 |
185 | @Override
186 | public String getMavenVersion() throws IOException, LauncherException {
187 | // TODO cleanup, there is no need to write log file, for example
188 |
189 | File logFile;
190 | try {
191 | logFile = File.createTempFile("maven", "log");
192 | } catch (IOException e) {
193 | throw new LauncherException("Error creating temp file", e);
194 | }
195 |
196 | // disable EMMA runtime controller port allocation, should be harmless if EMMA is not used
197 | Map envVars = Collections.singletonMap("MAVEN_OPTS", "-Demma.rt.control=false");
198 | run(new String[] {"--version"}, envVars, new File(""), new File(""), logFile);
199 |
200 | List logLines = Files.readAllLines(logFile.toPath(), Charset.defaultCharset());
201 | // noinspection ResultOfMethodCallIgnored
202 | logFile.delete();
203 |
204 | String version = extractMavenVersion(logLines);
205 |
206 | if (version == null) {
207 | throw new LauncherException(
208 | "Illegal Maven output: String 'Maven' not found in the following output:\n" + join(logLines, "\n"));
209 | } else {
210 | return version;
211 | }
212 | }
213 |
214 | private String join(List lines, String eol) {
215 | StringBuilder sb = new StringBuilder();
216 | for (String line : lines) {
217 | sb.append(line).append(eol);
218 | }
219 | return sb.toString();
220 | }
221 |
222 | static String extractMavenVersion(List logLines) {
223 | String version = null;
224 |
225 | final Pattern mavenVersion = Pattern.compile("(?i).*Maven.*? ([0-9]\\.\\S*).*");
226 |
227 | for (Iterator it = logLines.iterator(); version == null && it.hasNext(); ) {
228 | String line = it.next();
229 |
230 | Matcher m = mavenVersion.matcher(line);
231 | if (m.matches()) {
232 | version = m.group(1);
233 | }
234 | }
235 |
236 | return version;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/LauncherException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | /*
11 | * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
12 | * agreements. See the NOTICE file distributed with this work for additional information regarding
13 | * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
14 | * "License"); you may not use this file except in compliance with the License. You may obtain a
15 | * copy of the License at
16 | *
17 | * http://www.apache.org/licenses/LICENSE-2.0
18 | *
19 | * Unless required by applicable law or agreed to in writing, software distributed under the License
20 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21 | * or implied. See the License for the specific language governing permissions and limitations under
22 | * the License.
23 | */
24 |
25 | /**
26 | * @author Benjamin Bentmann
27 | */
28 | @SuppressWarnings("serial")
29 | class LauncherException extends Exception {
30 |
31 | public LauncherException(String message) {
32 | super(message);
33 | }
34 |
35 | public LauncherException(String message, Throwable cause) {
36 | super(message, cause);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenExecution.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import io.takari.maven.testing.TestProperties;
11 | import java.io.File;
12 | import java.nio.file.Files;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public class MavenExecution {
17 |
18 | private final MavenLauncher launcher;
19 |
20 | private final TestProperties properties;
21 |
22 | private final File multiModuleProjectDirectory;
23 |
24 | private final File basedir;
25 |
26 | private final List cliOptions = new ArrayList<>();
27 |
28 | MavenExecution(
29 | MavenLauncher launcher, TestProperties properties, File multiModuleProjectDirectory, String moduleRelpath) {
30 | this.launcher = launcher;
31 | this.properties = properties;
32 | this.multiModuleProjectDirectory = multiModuleProjectDirectory;
33 | this.basedir = moduleRelpath != null
34 | ? new File(multiModuleProjectDirectory, moduleRelpath)
35 | : multiModuleProjectDirectory;
36 | }
37 |
38 | public MavenExecutionResult execute(String... goals) throws Exception {
39 | File logFile = new File(basedir, "log.txt");
40 |
41 | List args = new ArrayList<>();
42 |
43 | File userSettings = properties.getUserSettings();
44 | if (userSettings != null && userSettings.isFile() && !isOption(cliOptions, "-s", true)) {
45 | args.add("-s");
46 | args.add(userSettings.getAbsolutePath());
47 | }
48 | if (!isOption(cliOptions, "-Dmaven.repo.local=", false)) {
49 | args.add("-Dmaven.repo.local=" + properties.getLocalRepository().getAbsolutePath());
50 | }
51 | args.add("-Dit-plugin.version=" + properties.getPluginVersion()); // TODO deprecated and remove
52 | args.add("-Dit-project.version=" + properties.getPluginVersion());
53 | args.addAll(cliOptions);
54 |
55 | for (String goal : goals) {
56 | args.add(goal);
57 | }
58 |
59 | try {
60 | launcher.run(args.toArray(new String[args.size()]), multiModuleProjectDirectory, basedir, logFile);
61 | } catch (Exception e) {
62 | String ciEnvar = System.getenv("CONTINUOUS_INTEGRATION");
63 | if (ciEnvar != null && ciEnvar.equalsIgnoreCase("true")) {
64 | String logFileContent = new String(Files.readAllBytes(logFile.toPath()));
65 | System.out.println(logFileContent);
66 | }
67 | throw e;
68 | }
69 |
70 | return new MavenExecutionResult(basedir, logFile);
71 | }
72 |
73 | private boolean isOption(List args, String str, boolean exact) {
74 | for (String arg : args) {
75 | if (exact && str.equals(arg)) {
76 | return true;
77 | } else if (!exact && arg.startsWith(str)) {
78 | return true;
79 | }
80 | }
81 | return false;
82 | }
83 |
84 | public MavenExecution withCliOption(String string) {
85 | cliOptions.add(string);
86 | return this;
87 | }
88 |
89 | public MavenExecution withCliOptions(String... strings) {
90 | for (String string : strings) {
91 | cliOptions.add(string);
92 | }
93 | return this;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenExecutionResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.nio.charset.Charset;
13 | import java.nio.file.Files;
14 | import java.util.ArrayList;
15 | import java.util.Collections;
16 | import java.util.List;
17 | import org.junit.Assert;
18 |
19 | // wraps maven invocation results
20 | public class MavenExecutionResult {
21 |
22 | private final File basedir;
23 | private final List log;
24 |
25 | MavenExecutionResult(File basedir, File logFile) throws IOException {
26 | this.basedir = basedir;
27 | List log = new ArrayList<>();
28 | if (logFile.canRead()) {
29 | for (String line : Files.readAllLines(logFile.toPath(), Charset.defaultCharset())) {
30 | log.add(line);
31 | }
32 | }
33 | this.log = Collections.unmodifiableList(log);
34 | }
35 |
36 | public MavenExecutionResult assertErrorFreeLog() throws Exception {
37 | List errors = new ArrayList<>();
38 | for (String line : log) {
39 | if (line.contains("[ERROR]")) {
40 | errors.add(line);
41 | }
42 | }
43 | Assert.assertTrue(errors.toString(), errors.isEmpty());
44 | return this;
45 | }
46 |
47 | public MavenExecutionResult assertLogText(String text) {
48 | for (String line : log) {
49 | if (line.contains(text)) {
50 | return this;
51 | }
52 | }
53 | Assert.fail("Log line not present: " + text);
54 | return this;
55 | }
56 |
57 | public MavenExecutionResult assertNoLogText(String text) {
58 | for (String line : log) {
59 | if (line.contains(text)) {
60 | Assert.fail("Log line present: " + text);
61 | }
62 | }
63 | return this;
64 | }
65 |
66 | public File getBasedir() {
67 | return basedir;
68 | }
69 |
70 | /**
71 | * @since 2.9.2
72 | */
73 | public List getLog() {
74 | return log;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenInstallationUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import java.io.BufferedInputStream;
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.net.URL;
16 | import java.util.Properties;
17 | import java.util.zip.ZipEntry;
18 | import java.util.zip.ZipFile;
19 | import org.codehaus.plexus.classworlds.ClassWorldException;
20 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException;
21 | import org.codehaus.plexus.classworlds.launcher.ConfigurationHandler;
22 | import org.codehaus.plexus.classworlds.launcher.ConfigurationParser;
23 | import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
24 | import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
25 |
26 | public class MavenInstallationUtils {
27 |
28 | public static final String MAVEN_CORE_POMPROPERTIES = "META-INF/maven/org.apache.maven/maven-core/pom.properties";
29 |
30 | public static final String SYSPROP_MAVEN_HOME = "maven.home";
31 |
32 | public static final String SYSPROP_CLASSWORLDSCONF = "classworlds.conf";
33 |
34 | public static final String SYSPROP_MAVEN_MAIN_CLASS = "maven.mainClass";
35 |
36 | public static final String MAVEN3_MAIN_CLASS = "org.apache.maven.cli.MavenCli";
37 |
38 | public static final String MAVEN4_MAIN_CLASS = "org.apache.maven.cling.MavenCling";
39 |
40 | public static String getMavenVersion(Class> clazz) throws IOException {
41 | try (InputStream is = clazz.getResourceAsStream("/" + MAVEN_CORE_POMPROPERTIES)) {
42 | return getMavenVersion(is);
43 | }
44 | }
45 |
46 | public static String getMavenVersion(InputStream is) throws IOException {
47 | Properties props = new Properties();
48 | if (is != null) {
49 | props.load(is);
50 | }
51 | return props.getProperty("version");
52 | }
53 |
54 | public static String getMavenVersion(File mavenHome, File classworldsConf) {
55 | classworldsConf = getClassworldsConf(mavenHome, classworldsConf);
56 |
57 | @SuppressWarnings("serial")
58 | class MavenVersionFoundException extends RuntimeException {
59 | public final String version;
60 |
61 | MavenVersionFoundException(String version) {
62 | this.version = version;
63 | }
64 | }
65 |
66 | class VersionConfigHandler implements ConfigurationHandler {
67 |
68 | @Override
69 | public void setAppMain(String mainClassName, String mainRealmName) {}
70 |
71 | @Override
72 | public void addRealm(String realmName) throws DuplicateRealmException {}
73 |
74 | @Override
75 | public void addImportFrom(String relamName, String importSpec) throws NoSuchRealmException {}
76 |
77 | @Override
78 | public void addLoadFile(File file) {
79 | String version = null;
80 | try {
81 | if (file.isFile()) {
82 | try (ZipFile zip = new ZipFile(file)) {
83 | ZipEntry entry = zip.getEntry(MAVEN_CORE_POMPROPERTIES);
84 | if (entry != null) {
85 | try (InputStream is = zip.getInputStream(entry)) {
86 | version = getMavenVersion(is);
87 | }
88 | }
89 | }
90 | } else {
91 | try (InputStream is = new BufferedInputStream(
92 | new FileInputStream(new File(file, MAVEN_CORE_POMPROPERTIES)))) {
93 | version = getMavenVersion(is);
94 | }
95 | }
96 | if (version != null) {
97 | throw new MavenVersionFoundException(version);
98 | }
99 | } catch (IOException e) {
100 | // assume the file does not have maven version
101 | }
102 | }
103 |
104 | @Override
105 | public void addLoadURL(URL url) {}
106 | }
107 | ;
108 |
109 | try {
110 | VersionConfigHandler configHandler = new VersionConfigHandler();
111 | Properties properties = new Properties(System.getProperties());
112 | properties.setProperty(SYSPROP_MAVEN_MAIN_CLASS, MAVEN4_MAIN_CLASS); // must for maven4, ignored by maven3
113 | properties.setProperty(SYSPROP_MAVEN_HOME, mavenHome.getCanonicalPath());
114 | ConfigurationParser configParser = new ConfigurationParser(configHandler, properties);
115 | try (InputStream is = new BufferedInputStream(new FileInputStream(classworldsConf))) {
116 | configParser.parse(is);
117 | }
118 | } catch (IOException | ClassWorldException | ConfigurationException e) {
119 | throw new IllegalArgumentException("Could not determine Maven version", e);
120 | } catch (MavenVersionFoundException e) {
121 | return e.version;
122 | }
123 |
124 | throw new IllegalArgumentException("Could not determine Maven version");
125 | }
126 |
127 | public static File getForcedClassworldsConf() {
128 | File configFile = null;
129 | String classworldConf = System.getProperty(SYSPROP_CLASSWORLDSCONF);
130 | String mavenHome = System.getProperty(SYSPROP_MAVEN_HOME);
131 | if (classworldConf != null) {
132 | configFile = new File(classworldConf);
133 | }
134 | if (configFile == null) {
135 | if (mavenHome != null) {
136 | configFile = new File(mavenHome, "bin/m2.conf");
137 | }
138 | }
139 | return configFile;
140 | }
141 |
142 | public static File getForcedMavenHome() {
143 | String mavenHome = System.getProperty(SYSPROP_MAVEN_HOME);
144 | if (mavenHome != null) {
145 | return new File(mavenHome);
146 | }
147 | return null;
148 | }
149 |
150 | public static File getClassworldsConf(File mavenHome, File classworldsConf) {
151 | if (classworldsConf == null) {
152 | classworldsConf = new File(mavenHome, "bin/m2.conf");
153 | }
154 | return classworldsConf;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenInstallations.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import java.lang.annotation.ElementType;
11 | import java.lang.annotation.Inherited;
12 | import java.lang.annotation.Retention;
13 | import java.lang.annotation.RetentionPolicy;
14 | import java.lang.annotation.Target;
15 |
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @Target(ElementType.TYPE)
18 | @Inherited
19 | public @interface MavenInstallations {
20 |
21 | /*
22 | * Note on installation names. In most cases it will be desirable to give custom distributions a meaningful name and use that name as part of test name. Most likely this will be useful not only for
23 | * integration tests but in other scenarios too (Hudson and m2e immediately come to mind), so the name/version need to be embedded in the maven installation itself. For example, it can be a file
24 | * under ${maven.home}/conf directory, similar to /etc/issue used by linux distributions. It is also possible to include distribution name/version in one of the jars, similar to how maven version is
25 | * already included in META-INF/maven/org.apache.maven/maven-core/pom.properties.
26 | */
27 | public String[] value();
28 | }
29 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenLauncher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | /*
11 | * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
12 | * agreements. See the NOTICE file distributed with this work for additional information regarding
13 | * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
14 | * "License"); you may not use this file except in compliance with the License. You may obtain a
15 | * copy of the License at
16 | *
17 | * http://www.apache.org/licenses/LICENSE-2.0
18 | *
19 | * Unless required by applicable law or agreed to in writing, software distributed under the License
20 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21 | * or implied. See the License for the specific language governing permissions and limitations under
22 | * the License.
23 | */
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 |
28 | /**
29 | * @author Benjamin Bentmann
30 | */
31 | interface MavenLauncher {
32 |
33 | int run(String[] cliArgs, File multiModuleProjectDirectory, File workingDirectory, File logFile)
34 | throws IOException, LauncherException;
35 |
36 | String getMavenVersion() throws IOException, LauncherException;
37 | }
38 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenRuntime.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import static org.eclipse.m2e.workspace.WorkspaceState2.SYSPROP_STATEFILE_LOCATION;
11 |
12 | import io.takari.maven.testing.TestProperties;
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.util.ArrayList;
16 | import java.util.Collection;
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 |
21 | public class MavenRuntime {
22 | private final MavenLauncher launcher;
23 |
24 | private final TestProperties properties;
25 |
26 | public static class MavenRuntimeBuilder {
27 |
28 | protected final TestProperties properties;
29 |
30 | protected final File mavenHome;
31 |
32 | protected final String mavenVersion;
33 |
34 | protected final File classworldsConf;
35 |
36 | protected final List extensions = new ArrayList<>();
37 |
38 | protected final List args = new ArrayList<>();
39 |
40 | MavenRuntimeBuilder(File mavenHome, File classworldsConf) {
41 | this.properties = new TestProperties();
42 | this.mavenHome = mavenHome;
43 | this.mavenVersion = MavenInstallationUtils.getMavenVersion(mavenHome, classworldsConf);
44 | this.classworldsConf = classworldsConf;
45 |
46 | StringBuilder workspaceState = new StringBuilder();
47 | appendLocation(workspaceState, System.getProperty(SYSPROP_STATEFILE_LOCATION));
48 | appendLocation(workspaceState, properties.get("workspaceStateProperties"));
49 |
50 | String workspaceResolver = properties.get("workspaceResolver");
51 | if (workspaceState.length() > 0 && isFile(workspaceResolver)) {
52 | if ("3.2.1".equals(mavenVersion)) {
53 | throw new IllegalArgumentException(
54 | "Maven 3.2.1 is not supported, see https://jira.codehaus.org/browse/MNG-5591");
55 | }
56 | args.add("-D" + SYSPROP_STATEFILE_LOCATION + "=" + workspaceState.toString());
57 | extensions.add(workspaceResolver);
58 | }
59 | // TODO decide if workspace resolution must be enabled and enforced
60 | }
61 |
62 | private void appendLocation(StringBuilder workspaceState, String location) {
63 | if (location != null) {
64 | if (!isFile(location)) {
65 | throw new IllegalArgumentException("Not a file " + location);
66 | }
67 | if (workspaceState.length() > 0) {
68 | workspaceState.append(File.pathSeparator);
69 | }
70 | workspaceState.append(location);
71 | }
72 | }
73 |
74 | MavenRuntimeBuilder(File mavenHome, File classworldsConf, List extensions, List args) {
75 | this.properties = new TestProperties();
76 | this.mavenHome = mavenHome;
77 | this.mavenVersion = MavenInstallationUtils.getMavenVersion(mavenHome, classworldsConf);
78 | this.classworldsConf = classworldsConf;
79 | this.extensions.addAll(extensions);
80 | this.args.addAll(args);
81 | }
82 |
83 | private static boolean isFile(String path) {
84 | return path != null && new File(path).isFile();
85 | }
86 |
87 | public MavenRuntimeBuilder withExtension(File extensionLocation) {
88 | assertFileExists("No such file or directory: " + extensionLocation, extensionLocation);
89 | extensions.add(extensionLocation.getAbsolutePath());
90 | return this;
91 | }
92 |
93 | public MavenRuntimeBuilder withExtensions(Collection extensionLocations) {
94 | for (File extensionLocation : extensionLocations) {
95 | assertFileExists("No such file or directory: " + extensionLocation, extensionLocation);
96 | extensions.add(extensionLocation.getAbsolutePath());
97 | }
98 | return this;
99 | }
100 |
101 | private void assertFileExists(String message, File file) {
102 | if (!file.exists()) {
103 | throw new AssertionError("No such file or directory: " + file);
104 | }
105 | }
106 |
107 | public MavenRuntimeBuilder withCliOptions(String... options) {
108 | for (String option : options) {
109 | args.add(option);
110 | }
111 | return this;
112 | }
113 |
114 | public ForkedMavenRuntimeBuilder forkedBuilder() {
115 | return new ForkedMavenRuntimeBuilder(mavenHome, classworldsConf, extensions, args);
116 | }
117 |
118 | public MavenRuntime build() throws Exception {
119 | Embedded3xLauncher launcher =
120 | Embedded3xLauncher.createFromMavenHome(mavenHome, classworldsConf, extensions, args);
121 | return new MavenRuntime(launcher, properties);
122 | }
123 | }
124 |
125 | public static class ForkedMavenRuntimeBuilder extends MavenRuntimeBuilder {
126 |
127 | private Map environment;
128 | private final List jvmArgs = new ArrayList<>();
129 |
130 | ForkedMavenRuntimeBuilder(File mavenHome, File classworldsConf) {
131 | super(mavenHome, classworldsConf);
132 | }
133 |
134 | ForkedMavenRuntimeBuilder(File mavenHome, File classworldsConf, List extensions, List args) {
135 | super(mavenHome, classworldsConf, extensions, args);
136 | }
137 |
138 | public ForkedMavenRuntimeBuilder withEnvironment(Map environment) {
139 | this.environment = new HashMap<>(environment);
140 | return this;
141 | }
142 |
143 | @Override
144 | public ForkedMavenRuntimeBuilder withExtension(File extensionLocation) {
145 | super.withExtension(extensionLocation);
146 | return this;
147 | }
148 |
149 | @Override
150 | public ForkedMavenRuntimeBuilder withExtensions(Collection extensionLocations) {
151 | super.withExtensions(extensionLocations);
152 | return this;
153 | }
154 |
155 | @Override
156 | public ForkedMavenRuntimeBuilder withCliOptions(String... options) {
157 | super.withCliOptions(options);
158 | return this;
159 | }
160 |
161 | /**
162 | * Adds a JVM option, as opposed to application option that can be added via {@link #withCliOptions(String...)}. Use this method to add options you would normally pass to {@code mvn/mvn.bat} via
163 | * {@code MAVEN_OPTS} environment variable.
164 | *
165 | * @param jvmOption the JVM option to add
166 | * @return this {@link ForkedMavenRuntimeBuilder}
167 | */
168 | public ForkedMavenRuntimeBuilder withJvmOption(String jvmOption) {
169 | this.jvmArgs.add(jvmOption);
170 | return this;
171 | }
172 |
173 | /**
174 | * Add a JVM options, as opposed to application options that can be added via {@link #withCliOptions(String...)}. Use this method to add options you would normally pass to {@code mvn/mvn.bat} via
175 | * {@code MAVEN_OPTS} environment variable.
176 | *
177 | * @param jvmOptions the JVM options to add
178 | * @return this {@link ForkedMavenRuntimeBuilder}
179 | */
180 | public ForkedMavenRuntimeBuilder withJvmOptions(String... jvmOptions) {
181 | for (String jvmArg : jvmOptions) {
182 | this.jvmArgs.add(jvmArg);
183 | }
184 | return this;
185 | }
186 |
187 | /**
188 | * Add a JVM options, as opposed to application options that can be added via {@link #withCliOptions(String...)}. Use this method to add options you would normally pass to {@code mvn/mvn.bat} via
189 | * {@code MAVEN_OPTS} environment variable.
190 | *
191 | * @param jvmOptions the JVM options to add
192 | * @return this {@link ForkedMavenRuntimeBuilder}
193 | */
194 | public ForkedMavenRuntimeBuilder withJvmOptions(Collection jvmOptions) {
195 | this.jvmArgs.addAll(jvmOptions);
196 | return this;
197 | }
198 |
199 | @Override
200 | public MavenRuntime build() {
201 | ForkedLauncher launcher =
202 | new ForkedLauncher(mavenHome, classworldsConf, extensions, environment, args, jvmArgs);
203 | return new MavenRuntime(launcher, properties);
204 | }
205 | }
206 |
207 | MavenRuntime(MavenLauncher launcher, TestProperties properties) {
208 | this.launcher = launcher;
209 | this.properties = properties;
210 | }
211 |
212 | public static MavenRuntimeBuilder builder(File mavenHome, File classworldsConf) {
213 | return new MavenRuntimeBuilder(mavenHome, classworldsConf);
214 | }
215 |
216 | public static ForkedMavenRuntimeBuilder forkedBuilder(File mavenHome) {
217 | return new ForkedMavenRuntimeBuilder(mavenHome, null);
218 | }
219 |
220 | public MavenExecution forProject(File multiModuleProjectDirectory) {
221 | return new MavenExecution(launcher, properties, multiModuleProjectDirectory, null);
222 | }
223 |
224 | /**
225 | * @since 2.9
226 | */
227 | public MavenExecution forProject(File multiModuleProjectDirectory, String moduleRelpath)
228 | throws IOException, LauncherException {
229 | String mavenVersion = launcher.getMavenVersion();
230 | if (!isVersion330plus(mavenVersion)) {
231 | throw new UnsupportedOperationException(
232 | "Explicit multiModuleProjectDirectory requires Maven 3.3 or newer, current version is "
233 | + mavenVersion);
234 | }
235 | return new MavenExecution(launcher, properties, multiModuleProjectDirectory, moduleRelpath);
236 | }
237 |
238 | private boolean isVersion330plus(String version) {
239 | String[] split = version.split("\\.");
240 | if (split.length < 2) {
241 | return false; // can't parse
242 | }
243 | if (Integer.parseInt(split[0]) < 3) {
244 | return false;
245 | }
246 | return Integer.parseInt(split[1]) >= 3;
247 | }
248 |
249 | /**
250 | * @since 2.0
251 | */
252 | public String getMavenVersion() throws IOException, LauncherException {
253 | return launcher.getMavenVersion();
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/MavenVersions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor;
9 |
10 | import java.lang.annotation.ElementType;
11 | import java.lang.annotation.Inherited;
12 | import java.lang.annotation.Retention;
13 | import java.lang.annotation.RetentionPolicy;
14 | import java.lang.annotation.Target;
15 |
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @Target(ElementType.TYPE)
18 | @Inherited
19 | public @interface MavenVersions {
20 | public String[] value();
21 | }
22 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/ForcedMavenRuntimeBuilderParameterResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenInstallationUtils;
11 | import io.takari.maven.testing.executor.MavenRuntime;
12 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
13 | import java.io.File;
14 | import org.junit.jupiter.api.extension.ExtensionContext;
15 | import org.junit.jupiter.api.extension.ParameterContext;
16 | import org.junit.jupiter.api.extension.ParameterResolutionException;
17 | import org.junit.jupiter.api.extension.ParameterResolver;
18 |
19 | /**
20 | * Provides a {@link MavenRuntimeBuilder} based on {@code -Dmaven.home}
21 | * and optionally {@code -Dclassworlds.conf}.
22 | *
23 | * @author Philippe Marschall
24 | */
25 | final class ForcedMavenRuntimeBuilderParameterResolver implements ParameterResolver {
26 |
27 | ForcedMavenRuntimeBuilderParameterResolver() {
28 | super();
29 | }
30 |
31 | @Override
32 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
33 | return (parameterContext.getParameter().getType() == MavenRuntimeBuilder.class) && MavenHomeUtils.isForced();
34 | }
35 |
36 | @Override
37 | public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
38 | throws ParameterResolutionException {
39 | File forcedMavenHome = MavenInstallationUtils.getForcedMavenHome();
40 | File forcedClassworldsConf = MavenInstallationUtils.getForcedClassworldsConf();
41 |
42 | if (forcedMavenHome != null) {
43 | if (forcedMavenHome.isDirectory() || ((forcedClassworldsConf != null) && forcedClassworldsConf.isFile())) {
44 | return MavenRuntime.builder(forcedMavenHome, forcedClassworldsConf);
45 | }
46 | }
47 | throw new ParameterResolutionException("Invalid -Dmaven.home=" + forcedMavenHome.getAbsolutePath());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenHomeUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenInstallationUtils;
11 | import java.io.File;
12 | import org.junit.jupiter.api.extension.ExtensionConfigurationException;
13 |
14 | final class MavenHomeUtils {
15 |
16 | private MavenHomeUtils() {
17 | throw new AssertionError("not instantiable");
18 | }
19 |
20 | static boolean isForced() {
21 |
22 | File forcedMavenHome = MavenInstallationUtils.getForcedMavenHome();
23 | File forcedClassworldsConf = MavenInstallationUtils.getForcedClassworldsConf();
24 |
25 | if (forcedMavenHome != null) {
26 | if (forcedMavenHome.isDirectory() || (forcedClassworldsConf != null && forcedClassworldsConf.isFile())) {
27 | return true;
28 | }
29 | throw new ExtensionConfigurationException("Invalid -Dmaven.home=" + forcedMavenHome.getAbsolutePath());
30 | }
31 | return false;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenInstallationDisplayNameFormatter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | final class MavenInstallationDisplayNameFormatter {
11 |
12 | private final String displayName;
13 |
14 | MavenInstallationDisplayNameFormatter(String displayName) {
15 | this.displayName = displayName;
16 | }
17 |
18 | String format(String mavenInstallation) {
19 | return this.displayName + '[' + mavenInstallation + ']';
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenInstallationTestInvocationContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.util.Collections;
13 | import java.util.List;
14 | import org.junit.jupiter.api.extension.Extension;
15 | import org.junit.jupiter.api.extension.ExtensionConfigurationException;
16 | import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
17 |
18 | final class MavenInstallationTestInvocationContext implements TestTemplateInvocationContext {
19 |
20 | private final MavenInstallationDisplayNameFormatter formatter;
21 | private final String installation;
22 |
23 | MavenInstallationTestInvocationContext(String installation, MavenInstallationDisplayNameFormatter formatter) {
24 | this.installation = installation;
25 | this.formatter = formatter;
26 | }
27 |
28 | @Override
29 | public String getDisplayName(int invocationIndex) {
30 | return this.formatter.format(this.installation);
31 | }
32 |
33 | @Override
34 | public List getAdditionalExtensions() {
35 | File mavenHome;
36 | try {
37 | mavenHome = new File(this.installation).getCanonicalFile();
38 | } catch (IOException e) {
39 | throw new ExtensionConfigurationException("could not access maven installation: " + this.installation, e);
40 | }
41 | if (mavenHome.isDirectory()) {
42 | return Collections.singletonList(new MavenRuntimeBuilderParameterResolver(mavenHome));
43 | } else {
44 | throw new ExtensionConfigurationException("Invalid maven installation location " + installation);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenInstallationsTestExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenInstallations;
11 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
12 | import java.util.Arrays;
13 | import java.util.stream.Stream;
14 | import org.junit.jupiter.api.extension.ExtensionContext;
15 | import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
16 | import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
17 |
18 | /**
19 | * Provides a {@link TestTemplateInvocationContext} with a {@link MavenRuntimeBuilder}
20 | * for each Maven installation configured through {@link MavenInstallations}.
21 | *
22 | * Not active if Maven home is forced through {@code -Dmaven.home}.
23 | *
24 | * @author Philippe Marschall
25 | */
26 | final class MavenInstallationsTestExtension implements TestTemplateInvocationContextProvider {
27 |
28 | @Override
29 | public boolean supportsTestTemplate(ExtensionContext context) {
30 | if (MavenHomeUtils.isForced()) {
31 | return false;
32 | }
33 | // @formatter:off
34 | return context.getTestClass()
35 | .map(clazz -> clazz.isAnnotationPresent(MavenInstallations.class))
36 | .orElse(false);
37 | // @formatter:on
38 | }
39 |
40 | @Override
41 | public Stream provideTestTemplateInvocationContexts(ExtensionContext context) {
42 | String displayName = context.getDisplayName();
43 | String[] installations = context.getTestClass()
44 | .orElseThrow()
45 | .getAnnotation(MavenInstallations.class)
46 | .value();
47 | return Arrays.stream(installations)
48 | .map(installation -> new MavenInstallationTestInvocationContext(
49 | installation, new MavenInstallationDisplayNameFormatter(displayName)));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenJUnitTestRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenInstallationUtils;
11 | import io.takari.maven.testing.executor.MavenInstallations;
12 | import io.takari.maven.testing.executor.MavenRuntime;
13 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
14 | import io.takari.maven.testing.executor.MavenVersions;
15 | import java.io.File;
16 | import java.util.ArrayList;
17 | import java.util.Collections;
18 | import java.util.List;
19 | import org.junit.runner.Runner;
20 | import org.junit.runner.notification.RunNotifier;
21 | import org.junit.runners.BlockJUnit4ClassRunner;
22 | import org.junit.runners.Suite;
23 | import org.junit.runners.model.FrameworkMethod;
24 | import org.junit.runners.model.InitializationError;
25 | import org.junit.runners.model.Statement;
26 |
27 | /**
28 | * Runs JUnit4 tests with one or more Maven runtimes. The test class must have public constructor with single parameter of type {@linkplain MavenRuntimeBuilder MavenRuntimeBuilder}.
29 | *
30 | * Test Maven runtimes are located in the following order:
31 | *
32 | *
33 | * - If {@code -Dmaven.home} (and optionally {@code -Dclassworlds.conf}) is specified, the tests will be executed with the specified Maven installation. This is what is used in Eclipse Maven JUnit
34 | * Test launch configuration to implement "Override test Maven runtime". Can also be useful to run tests with custom or snapshot Maven build specified from pom.xml.
35 | * - If {@linkplain MavenInstallations @MavenInstallations} and/or {@linkplain MavenVersions @MavenVersions} is specified, the tests will run with all configured Maven installations and versions
36 | *
37 | */
38 | public class MavenJUnitTestRunner extends Suite {
39 |
40 | private static class SingleMavenInstallationRunner extends BlockJUnit4ClassRunner {
41 |
42 | private final File mavenHome;
43 |
44 | private final File classworldsConf;
45 |
46 | private final String name;
47 |
48 | SingleMavenInstallationRunner(Class> klass, File mavenHome, File classworldsConf, String name)
49 | throws InitializationError {
50 | super(klass);
51 | this.mavenHome = mavenHome;
52 | this.classworldsConf = classworldsConf;
53 | this.name = name;
54 | }
55 |
56 | @Override
57 | protected Object createTest() throws Exception {
58 | MavenRuntimeBuilder builder = MavenRuntime.builder(mavenHome, classworldsConf);
59 | return getTestClass()
60 | .getJavaClass()
61 | .getConstructor(MavenRuntimeBuilder.class)
62 | .newInstance(builder);
63 | }
64 |
65 | @Override
66 | protected void validateZeroArgConstructor(List errors) {
67 | try {
68 | getTestClass().getJavaClass().getConstructor(MavenRuntimeBuilder.class);
69 | } catch (NoSuchMethodException e) {
70 | errors.add(e);
71 | }
72 | }
73 |
74 | @Override
75 | protected String getName() {
76 | return "[" + name + "]";
77 | }
78 |
79 | @Override
80 | protected String testName(FrameworkMethod method) {
81 | return method.getName() + getName();
82 | }
83 |
84 | @Override
85 | protected Statement classBlock(RunNotifier notifier) {
86 | return childrenInvoker(notifier);
87 | }
88 | }
89 |
90 | public MavenJUnitTestRunner(Class> clazz) throws Throwable {
91 | super(clazz, getRunners(clazz));
92 | }
93 |
94 | private static List getRunners(final Class> clazz) throws Throwable {
95 | File forcedMavenHome = MavenInstallationUtils.getForcedMavenHome();
96 | File forcedClassworldsConf = MavenInstallationUtils.getForcedClassworldsConf();
97 |
98 | if (forcedMavenHome != null) {
99 | if (forcedMavenHome.isDirectory() || (forcedClassworldsConf != null && forcedClassworldsConf.isFile())) {
100 | String version = MavenInstallationUtils.getMavenVersion(forcedMavenHome, forcedClassworldsConf);
101 | return Collections.singletonList(
102 | new SingleMavenInstallationRunner(clazz, forcedMavenHome, forcedClassworldsConf, version));
103 | }
104 | throw new InitializationError(new Exception("Invalid -Dmaven.home=" + forcedMavenHome.getAbsolutePath()));
105 | }
106 |
107 | final List errors = new ArrayList<>();
108 | final List runners = new ArrayList<>();
109 |
110 | MavenInstallations installations = clazz.getAnnotation(MavenInstallations.class);
111 | if (installations != null) {
112 | for (String installation : installations.value()) {
113 | File mavenHome = new File(installation).getCanonicalFile();
114 | if (mavenHome.isDirectory()) {
115 | runners.add(new SingleMavenInstallationRunner(clazz, mavenHome, null, installation));
116 | } else {
117 | errors.add(new Exception("Invalid maven installation location " + installation));
118 | }
119 | }
120 | }
121 |
122 | MavenVersions versions = clazz.getAnnotation(MavenVersions.class);
123 | if (versions != null) {
124 | new MavenVersionResolver() {
125 | @Override
126 | protected void resolved(File mavenHome, String version) throws InitializationError {
127 | runners.add(new SingleMavenInstallationRunner(clazz, mavenHome, null, version));
128 | }
129 |
130 | @Override
131 | protected void error(String version, Exception cause) {
132 | errors.add(new Exception("Could not resolve maven version " + version, cause));
133 | }
134 | }.resolve(versions.value());
135 | }
136 |
137 | if (!errors.isEmpty()) {
138 | throw new InitializationError(errors);
139 | }
140 |
141 | if (runners.isEmpty()) {
142 | throw new InitializationError(new Exception("No configured test maven runtime"));
143 | }
144 |
145 | return runners;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenPluginTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
11 | import static java.lang.annotation.ElementType.METHOD;
12 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
13 |
14 | import java.lang.annotation.Documented;
15 | import java.lang.annotation.Inherited;
16 | import java.lang.annotation.Retention;
17 | import java.lang.annotation.Target;
18 | import org.junit.jupiter.api.TestTemplate;
19 | import org.junit.jupiter.api.extension.ExtendWith;
20 |
21 | /**
22 | * Meta-annotation for Maven plugin integration tests, registers all necessary extensions.
23 | *
24 | * @author Philippe Marschall
25 | */
26 | @Documented
27 | @Retention(RUNTIME)
28 | @Target({ANNOTATION_TYPE, METHOD})
29 | @TestTemplate
30 | @Inherited
31 | @ExtendWith({
32 | ForcedMavenRuntimeBuilderParameterResolver.class,
33 | MavenInstallationsTestExtension.class,
34 | MavenVersionsTestExtension.class
35 | })
36 | public @interface MavenPluginTest {}
37 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenRuntimeBuilderParameterResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenRuntime;
11 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
12 | import java.io.File;
13 | import org.junit.jupiter.api.extension.ExtensionContext;
14 | import org.junit.jupiter.api.extension.ParameterContext;
15 | import org.junit.jupiter.api.extension.ParameterResolutionException;
16 | import org.junit.jupiter.api.extension.ParameterResolver;
17 |
18 | final class MavenRuntimeBuilderParameterResolver implements ParameterResolver {
19 |
20 | private final File mavenHome;
21 |
22 | MavenRuntimeBuilderParameterResolver(File mavenHome) {
23 | this.mavenHome = mavenHome;
24 | }
25 |
26 | @Override
27 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
28 | return parameterContext.getParameter().getType() == MavenRuntimeBuilder.class;
29 | }
30 |
31 | @Override
32 | public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
33 | throws ParameterResolutionException {
34 | return MavenRuntime.builder(this.mavenHome, null);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenVersionDisplayNameFormatter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | final class MavenVersionDisplayNameFormatter {
11 |
12 | private final String displayName;
13 |
14 | MavenVersionDisplayNameFormatter(String displayName) {
15 | this.displayName = displayName;
16 | }
17 |
18 | String format(String mavenVersion) {
19 | return this.displayName + '[' + mavenVersion + ']';
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenVersionTestInvocationContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import java.io.File;
11 | import java.util.Collections;
12 | import java.util.List;
13 | import org.junit.jupiter.api.extension.Extension;
14 | import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
15 |
16 | final class MavenVersionTestInvocationContext implements TestTemplateInvocationContext {
17 |
18 | private final String mavenVersion;
19 | private final File mavenHome;
20 | private final MavenVersionDisplayNameFormatter formatter;
21 |
22 | MavenVersionTestInvocationContext(String mavenVersion, File mavenHome, MavenVersionDisplayNameFormatter formatter) {
23 | this.mavenVersion = mavenVersion;
24 | this.mavenHome = mavenHome;
25 | this.formatter = formatter;
26 | }
27 |
28 | @Override
29 | public String getDisplayName(int invocationIndex) {
30 | return this.formatter.format(this.mavenVersion);
31 | }
32 |
33 | @Override
34 | public List getAdditionalExtensions() {
35 | return Collections.singletonList(new MavenRuntimeBuilderParameterResolver(this.mavenHome));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/takari-plugin-testing/src/main/java/io/takari/maven/testing/executor/junit/MavenVersionsTestExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2024 Takari, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * https://www.eclipse.org/legal/epl-v10.html
7 | */
8 | package io.takari.maven.testing.executor.junit;
9 |
10 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
11 | import io.takari.maven.testing.executor.MavenVersions;
12 | import java.io.File;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.stream.Stream;
16 | import org.junit.jupiter.api.extension.ExtensionConfigurationException;
17 | import org.junit.jupiter.api.extension.ExtensionContext;
18 | import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
19 | import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
20 | import org.junit.runners.model.InitializationError;
21 |
22 | /**
23 | * Provides a {@link TestTemplateInvocationContext} with a {@link MavenRuntimeBuilder}
24 | * for each Maven version configured through {@link MavenVersions}.
25 | *
26 | * Not active if Maven home is forced through {@code -Dmaven.home}.
27 | *
28 | * @author Philippe Marschall
29 | */
30 | final class MavenVersionsTestExtension implements TestTemplateInvocationContextProvider {
31 |
32 | @Override
33 | public boolean supportsTestTemplate(ExtensionContext context) {
34 | if (MavenHomeUtils.isForced()) {
35 | return false;
36 | }
37 | // @formatter:off
38 | return context.getTestClass()
39 | .map(clazz -> clazz.isAnnotationPresent(MavenVersions.class))
40 | .orElse(false);
41 | // @formatter:on
42 | }
43 |
44 | @Override
45 | public Stream provideTestTemplateInvocationContexts(ExtensionContext context) {
46 | String displayName = context.getDisplayName();
47 | String[] versions = context.getTestClass()
48 | .orElseThrow()
49 | .getAnnotation(MavenVersions.class)
50 | .value();
51 | List contexts = new ArrayList<>();
52 | List errors = new ArrayList<>();
53 |
54 | try {
55 | new MavenVersionResolver() {
56 | @Override
57 | protected void resolved(File mavenHome, String version) throws InitializationError {
58 | contexts.add(new MavenVersionTestInvocationContext(
59 | version, mavenHome, new MavenVersionDisplayNameFormatter(displayName)));
60 | }
61 |
62 | @Override
63 | protected void error(String version, Exception cause) {
64 | errors.add(new Exception("Could not resolve maven version " + version, cause));
65 | }
66 | }.resolve(versions);
67 | } catch (Exception e) {
68 | throw new ExtensionConfigurationException("Could not resolve maven versions", e);
69 | }
70 | if (!errors.isEmpty()) {
71 | ExtensionConfigurationException extensionConfigurationException =
72 | new ExtensionConfigurationException("Could not resolve maven versions");
73 | for (Throwable error : errors) {
74 | extensionConfigurationException.addSuppressed(error);
75 | }
76 | throw extensionConfigurationException;
77 | }
78 | return contexts.stream();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/testproperties.md:
--------------------------------------------------------------------------------
1 | test.properties
2 | ===============
3 |
4 | Takari plugin testing harness requires presence of `test.properties` file
5 | on project test classpath. This file is used to pass various aspects of the
6 | running "outer" build to the test jvm. This includes project groupId,
7 | artifactId and version, location of user settings.xml and local repository,
8 | and so on.
9 |
10 | For projects using takari-maven-plugin packaging type, `test.properties`
11 | file is generated by takari-lifecycle-plugin:testProperties goal bound to
12 | the project default lifecycle. No explicit configuration is necessary.
13 |
14 | For other project types, like takari-jar for example, generation of
15 | `test.properties` file must be explicitly configured in project pom.xml file.
16 |
17 |
18 | io.takari.maven.plugins
19 | takari-lifecycle-plugin
20 | ${takariLifecycleVersion}
21 | true
22 |
23 |
24 | testProperties
25 | process-test-resources
26 |
27 | testProperties
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------