View Javadoc
1   /*
2    * Copyright 2013-2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.cloud.contract.maven.verifier;
18  
19  import java.io.File;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.maven.execution.MavenSession;
25  import org.apache.maven.model.Dependency;
26  import org.apache.maven.model.Resource;
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecution;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  import org.apache.maven.plugins.annotations.LifecyclePhase;
32  import org.apache.maven.plugins.annotations.Mojo;
33  import org.apache.maven.plugins.annotations.Parameter;
34  import org.apache.maven.plugins.annotations.ResolutionScope;
35  import org.apache.maven.project.MavenProject;
36  import org.eclipse.aether.RepositorySystemSession;
37  
38  import org.springframework.cloud.contract.spec.ContractVerifierException;
39  import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
40  import org.springframework.cloud.contract.verifier.TestGenerator;
41  import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
42  import org.springframework.cloud.contract.verifier.config.TestFramework;
43  import org.springframework.cloud.contract.verifier.config.TestMode;
44  
45  /**
46   * From the provided directory with contracts generates the acceptance tests on the
47   * producer side.
48   *
49   * @author Mariusz Smykula
50   */
51  @Mojo(name = "generateTests", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES,
52  		requiresDependencyResolution = ResolutionScope.TEST)
53  public class GenerateTestsMojo extends AbstractMojo {
54  
55  	@Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
56  	private RepositorySystemSession repoSession;
57  
58  	@Parameter(property = "spring.cloud.contract.verifier.contractsDirectory",
59  			defaultValue = "${project.basedir}/src/test/resources/contracts")
60  	private File contractsDirectory;
61  
62  	@Parameter(defaultValue = "${project.build.directory}/generated-test-sources/contracts")
63  	private File generatedTestSourcesDir;
64  
65  	@Parameter(defaultValue = "${project.build.directory}/generated-test-resources/contracts")
66  	private File generatedTestResourcesDir;
67  
68  	@Parameter
69  	private String basePackageForTests;
70  
71  	@Parameter
72  	private String baseClassForTests;
73  
74  	@Parameter(defaultValue = "MOCKMVC")
75  	private TestMode testMode;
76  
77  	@Parameter(defaultValue = "JUNIT5")
78  	private TestFramework testFramework;
79  
80  	@Parameter
81  	private String ruleClassForTests;
82  
83  	@Parameter
84  	private String nameSuffixForTests;
85  
86  	/**
87  	 * Imports that should be added to generated tests.
88  	 */
89  	@Parameter
90  	private String[] imports;
91  
92  	/**
93  	 * Static imports that should be added to generated tests.
94  	 */
95  	@Parameter
96  	private String[] staticImports;
97  
98  	/**
99  	 * Patterns that should not be taken into account for processing.
100 	 */
101 	@Parameter
102 	private List<String> excludedFiles;
103 
104 	/**
105 	 * Patterns that should be taken into account for processing.
106 	 */
107 	@Parameter(property = "includedFiles")
108 	private List<String> includedFiles;
109 
110 	/**
111 	 * Incubating feature. You can check the size of JSON arrays. If not turned on
112 	 * explicitly will be disabled.
113 	 */
114 	@Parameter(property = "spring.cloud.contract.verifier.assert.size", defaultValue = "false")
115 	private boolean assertJsonSize;
116 
117 	/**
118 	 * Patterns for which Spring Cloud Contract Verifier should generate @Ignored tests.
119 	 */
120 	@Parameter
121 	private List<String> ignoredFiles;
122 
123 	@Parameter(defaultValue = "${project}", readonly = true)
124 	private MavenProject project;
125 
126 	@Parameter(property = "spring.cloud.contract.verifier.skip", defaultValue = "false")
127 	private boolean skip;
128 
129 	@Parameter(property = "maven.test.skip", defaultValue = "false")
130 	private boolean mavenTestSkip;
131 
132 	/**
133 	 * The URL from which a contracts should get downloaded. If not provided but
134 	 * artifactid / coordinates notation was provided then the current Maven's build
135 	 * repositories will be taken into consideration.
136 	 */
137 	@Parameter(property = "contractsRepositoryUrl")
138 	private String contractsRepositoryUrl;
139 
140 	@Parameter(property = "contractDependency")
141 	private Dependency contractDependency;
142 
143 	/**
144 	 * The path in the JAR with all the contracts where contracts for this particular
145 	 * service lay. If not provided will be resolved to {@code groupid/artifactid}.
146 	 * Example: If {@code groupid} is {@code com.example} and {@code artifactid} is
147 	 * {@code service} then the resolved path will be {@code /com/example/artifactid}
148 	 */
149 	@Parameter(property = "contractsPath")
150 	private String contractsPath;
151 
152 	/**
153 	 * Picks the mode in which stubs will be found and registered.
154 	 */
155 	@Parameter(property = "contractsMode", defaultValue = "CLASSPATH")
156 	private StubRunnerProperties.StubsMode contractsMode;
157 
158 	/**
159 	 * A package that contains all the base clases for generated tests. If your contract
160 	 * resides in a location {@code src/test/resources/contracts/com/example/v1/} and you
161 	 * provide the {@code packageWithBaseClasses} value to
162 	 * {@code com.example.contracts.base} then we will search for a test source file that
163 	 * will have the package {@code com.example.contracts.base} and name
164 	 * {@code ExampleV1Base}. As you can see it will take the two last folders to and
165 	 * attach {@code Base} to its name.
166 	 */
167 	@Parameter(property = "packageWithBaseClasses")
168 	private String packageWithBaseClasses;
169 
170 	/**
171 	 * A way to override any base class mappings. The keys are regular expressions on the
172 	 * package name of the contract and the values FQN to a base class for that given
173 	 * expression. Example of a mapping {@code .*.com.example.v1..*} ->
174 	 * {@code com.example.SomeBaseClass} When a contract's package matches the provided
175 	 * regular expression then extending class will be the one provided in the map - in
176 	 * this case {@code com.example.SomeBaseClass}.
177 	 */
178 	@Parameter(property = "baseClassMappings")
179 	private List<BaseClassMapping> baseClassMappings;
180 
181 	/**
182 	 * The user name to be used to connect to the repo with contracts.
183 	 */
184 	@Parameter(property = "contractsRepositoryUsername")
185 	private String contractsRepositoryUsername;
186 
187 	/**
188 	 * The password to be used to connect to the repo with contracts.
189 	 */
190 	@Parameter(property = "contractsRepositoryPassword")
191 	private String contractsRepositoryPassword;
192 
193 	/**
194 	 * The proxy host to be used to connect to the repo with contracts.
195 	 */
196 	@Parameter(property = "contractsRepositoryProxyHost")
197 	private String contractsRepositoryProxyHost;
198 
199 	/**
200 	 * The proxy port to be used to connect to the repo with contracts.
201 	 */
202 	@Parameter(property = "contractsRepositoryProxyPort")
203 	private Integer contractsRepositoryProxyPort;
204 
205 	/**
206 	 * If set to {@code false} will NOT delete stubs from a temporary folder after running
207 	 * tests.
208 	 */
209 	@Parameter(property = "deleteStubsAfterTest", defaultValue = "true")
210 	private boolean deleteStubsAfterTest;
211 
212 	/**
213 	 * Map of properties that can be passed to custom
214 	 * {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
215 	 */
216 	@Parameter(property = "contractsProperties")
217 	private Map<String, String> contractsProperties = new HashMap<>();
218 
219 	/**
220 	 * When enabled, this flag will tell stub runner to throw an exception when no stubs /
221 	 * contracts were found.
222 	 */
223 	@Parameter(property = "failOnNoContracts", defaultValue = "true")
224 	private boolean failOnNoContracts;
225 
226 	/**
227 	 * If set to true then if any contracts that are in progress are found, will break the
228 	 * build. On the producer side you need to be explicit about the fact that you have
229 	 * contracts in progress and take into consideration that you might be causing false
230 	 * positive test execution results on the consumer side.
231 	 */
232 	@Parameter(property = "failOnInProgress", defaultValue = "true")
233 	private boolean failOnInProgress = true;
234 
235 	/**
236 	 * If set to true then tests are created only when contracts have changed since last
237 	 * build.
238 	 */
239 	@Parameter(property = "incrementalContractTests", defaultValue = "true")
240 	private boolean incrementalContractTests = true;
241 
242 	@Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
243 	private MojoExecution mojoExecution;
244 
245 	@Parameter(defaultValue = "${session}", readonly = true, required = true)
246 	private MavenSession session;
247 
248 	@Override
249 	public void execute() throws MojoExecutionException, MojoFailureException {
250 		if (this.skip || this.mavenTestSkip) {
251 			if (this.skip) {
252 				getLog().info("Skipping Spring Cloud Contract Verifier execution: spring.cloud.contract.verifier.skip="
253 						+ this.skip);
254 			}
255 			if (this.mavenTestSkip) {
256 				getLog().info(
257 						"Skipping Spring Cloud Contract Verifier execution: maven.test.skip=" + this.mavenTestSkip);
258 			}
259 			return;
260 		}
261 		getLog().info("Generating server tests source code for Spring Cloud Contract Verifier contract verification");
262 		final ContractVerifierConfigProperties config = new ContractVerifierConfigProperties();
263 		config.setFailOnInProgress(this.failOnInProgress);
264 		// download contracts, unzip them and pass as output directory
265 		File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency,
266 				this.contractsPath, this.contractsRepositoryUrl, this.contractsMode, getLog(),
267 				this.contractsRepositoryUsername, this.contractsRepositoryPassword, this.contractsRepositoryProxyHost,
268 				this.contractsRepositoryProxyPort, this.deleteStubsAfterTest, this.contractsProperties,
269 				this.failOnNoContracts).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory);
270 		getLog().info("Directory with contract is present at [" + contractsDirectory + "]");
271 
272 		if (this.incrementalContractTests
273 				&& !ChangeDetector.inputFilesChangeDetected(contractsDirectory, mojoExecution, session)) {
274 			getLog().info("Nothing to generate - all classes are up to date");
275 			return;
276 		}
277 
278 		setupConfig(config, contractsDirectory);
279 		this.project.addTestCompileSourceRoot(this.generatedTestSourcesDir.getAbsolutePath());
280 		Resource resource = new Resource();
281 		resource.setDirectory(this.generatedTestResourcesDir.getAbsolutePath());
282 		this.project.addTestResource(resource);
283 		if (getLog().isInfoEnabled()) {
284 			getLog().info("Test Source directory: " + this.generatedTestSourcesDir.getAbsolutePath() + " added.");
285 			getLog().info("Using [" + config.getBaseClassForTests() + "] as base class for test classes, ["
286 					+ config.getBasePackageForTests() + "] as base " + "package for tests, ["
287 					+ config.getPackageWithBaseClasses() + "] as package with " + "base classes, base class mappings "
288 					+ this.baseClassMappings);
289 		}
290 		try {
291 			LeftOverPrevention leftOverPrevention = new LeftOverPrevention(this.generatedTestSourcesDir, mojoExecution,
292 					session);
293 			TestGenerator generator = new TestGenerator(config);
294 			int generatedClasses = generator.generate();
295 			getLog().info("Generated " + generatedClasses + " test classes.");
296 			leftOverPrevention.deleteLeftOvers();
297 		}
298 		catch (ContractVerifierException e) {
299 			throw new MojoExecutionException(
300 					String.format("Spring Cloud Contract Verifier Plugin exception: %s", e.getMessage()), e);
301 		}
302 	}
303 
304 	private void setupConfig(ContractVerifierConfigProperties config, File contractsDirectory) {
305 		config.setContractsDslDir(contractsDirectory);
306 		config.setGeneratedTestSourcesDir(this.generatedTestSourcesDir);
307 		config.setGeneratedTestResourcesDir(this.generatedTestResourcesDir);
308 		config.setTestFramework(this.testFramework);
309 		config.setTestMode(this.testMode);
310 		config.setBasePackageForTests(this.basePackageForTests);
311 		config.setBaseClassForTests(this.baseClassForTests);
312 		config.setRuleClassForTests(this.ruleClassForTests);
313 		config.setNameSuffixForTests(this.nameSuffixForTests);
314 		config.setImports(this.imports);
315 		config.setStaticImports(this.staticImports);
316 		config.setIgnoredFiles(this.ignoredFiles);
317 		config.setExcludedFiles(this.excludedFiles);
318 		config.setIncludedFiles(this.includedFiles);
319 		config.setAssertJsonSize(this.assertJsonSize);
320 		config.setPackageWithBaseClasses(this.packageWithBaseClasses);
321 		if (this.baseClassMappings != null) {
322 			config.setBaseClassMappings(mappingsToMap());
323 		}
324 	}
325 
326 	public Map<String, String> mappingsToMap() {
327 		Map<String, String> map = new HashMap<>();
328 		if (this.baseClassMappings == null) {
329 			return map;
330 		}
331 		for (BaseClassMapping mapping : this.baseClassMappings) {
332 			map.put(mapping.getContractPackageRegex(), mapping.getBaseClassFQN());
333 		}
334 		return map;
335 	}
336 
337 	public List<String> getExcludedFiles() {
338 		return this.excludedFiles;
339 	}
340 
341 	public void setExcludedFiles(List<String> excludedFiles) {
342 		this.excludedFiles = excludedFiles;
343 	}
344 
345 	public List<String> getIgnoredFiles() {
346 		return this.ignoredFiles;
347 	}
348 
349 	public void setIgnoredFiles(List<String> ignoredFiles) {
350 		this.ignoredFiles = ignoredFiles;
351 	}
352 
353 	public boolean isAssertJsonSize() {
354 		return this.assertJsonSize;
355 	}
356 
357 	public void setAssertJsonSize(boolean assertJsonSize) {
358 		this.assertJsonSize = assertJsonSize;
359 	}
360 
361 }