blob: 7df7e5ce536b63a2b64e27e84e7c84945e027616 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.it;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.shared.utils.io.FileUtils;
import org.apache.maven.shared.verifier.VerificationException;
import org.apache.maven.shared.verifier.Verifier;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.opentest4j.TestAbortedException;
/**
* @author Jason van Zyl
* @author Kenney Westerhof
*/
public abstract class AbstractMavenIntegrationTestCase {
/**
* Save System.out for progress reports etc.
*/
private static PrintStream out = System.out;
/**
* The format for elapsed time.
*/
private static final DecimalFormat SECS_FORMAT =
new DecimalFormat("(0.0 s)", new DecimalFormatSymbols(Locale.ENGLISH));
/**
* The zero-based column index where to print the test result.
*/
private static final int RESULT_COLUMN = 60;
private boolean skip;
private BrokenMavenVersionException invert;
private static ArtifactVersion javaVersion;
private ArtifactVersion mavenVersion;
private VersionRange versionRange;
private String matchPattern;
private String testName;
private static final String DEFAULT_MATCH_PATTERN = "(.*?)-(RC[0-9]+|SNAPSHOT|RC[0-9]+-SNAPSHOT)";
protected static final String ALL_MAVEN_VERSIONS = "[2.0,)";
protected AbstractMavenIntegrationTestCase(String versionRangeStr) {
this(versionRangeStr, DEFAULT_MATCH_PATTERN);
}
protected AbstractMavenIntegrationTestCase(String versionRangeStr, String matchPattern) {
this.matchPattern = matchPattern;
requiresMavenVersion(versionRangeStr);
}
@BeforeAll
static void setupInputStream() {
if (!(System.in instanceof NonCloseableInputStream)) {
System.setIn(new NonCloseableInputStream(System.in));
}
}
/**
* Gets the Java version used to run this test.
*
* @return The Java version, never <code>null</code>.
*/
private ArtifactVersion getJavaVersion() {
if (javaVersion == null) {
String version = System.getProperty("java.version");
version = version.replaceAll("[_-]", ".");
Matcher matcher =
Pattern.compile("(?s).*?(([0-9]+\\.[0-9]+)(\\.[0-9]+)?).*").matcher(version);
if (matcher.matches()) {
version = matcher.group(1);
}
javaVersion = new DefaultArtifactVersion(version);
}
return javaVersion;
}
/**
* Gets the Maven version used to run this test.
*
* @return The Maven version or <code>null</code> if unknown.
*/
protected final ArtifactVersion getMavenVersion() {
if (mavenVersion == null) {
String version = System.getProperty("maven.version", "");
if (version.length() <= 0 || version.startsWith("${")) {
try {
Verifier verifier = new Verifier("");
version = verifier.getMavenVersion();
System.setProperty("maven.version", version);
} catch (VerificationException e) {
e.printStackTrace();
}
}
// NOTE: If the version looks like "${...}" it has been configured from an undefined expression
if (version != null && version.length() > 0 && !version.startsWith("${")) {
mavenVersion = new DefaultArtifactVersion(version);
}
}
return mavenVersion;
}
/**
* This allows fine-grained control over execution of individual test methods
* by allowing tests to adjust to the current Maven version, or else simply avoid
* executing altogether if the wrong version is present.
*/
protected boolean matchesVersionRange(String versionRangeStr) {
VersionRange versionRange;
try {
versionRange = VersionRange.createFromVersionSpec(versionRangeStr);
} catch (InvalidVersionSpecificationException e) {
throw (RuntimeException) new IllegalArgumentException("Invalid version range: " + versionRangeStr, e);
}
ArtifactVersion version = getMavenVersion();
if (version != null) {
return versionRange.containsVersion(removePattern(version));
} else {
out.println("WARNING: " + getITName() + ": version range '" + versionRange
+ "' supplied but no Maven version found - returning true for match check.");
return true;
}
}
/**
* Can be called by version specific setUp calls
*
* @return
*/
protected final boolean isSkipped() {
return skip;
}
protected void runTest() throws Throwable {
String testName = getTestName();
if (testName.startsWith("mng") || Character.isDigit(testName.charAt(0))) {
int mng = 4;
while (Character.isDigit(testName.charAt(mng))) {
mng++;
}
out.print(AnsiSupport.bold(testName.substring(0, mng)));
out.print(' ');
out.print(testName.substring(mng));
} else {
int index = testName.indexOf(' ');
if (index == -1) {
out.print(testName);
} else {
out.print(AnsiSupport.bold(testName.substring(0, index)));
out.print(testName.substring(index));
}
out.print('.');
}
out.print(pad(RESULT_COLUMN - testName.length()));
out.print(' ');
if (skip) {
out.println(AnsiSupport.warning("SKIPPED") + " - Maven version " + getMavenVersion() + " not in range "
+ versionRange);
return;
}
if ("true".equals(System.getProperty("useEmptyLocalRepository", "false"))) {
setupLocalRepo();
}
invert = null;
long milliseconds = System.currentTimeMillis();
try {
// TODO: JUNIT5
// super.runTest();
milliseconds = System.currentTimeMillis() - milliseconds;
if (invert != null) {
throw invert;
}
out.println(AnsiSupport.success("OK") + " " + formatTime(milliseconds));
} catch (UnsupportedJavaVersionException e) {
out.println(AnsiSupport.warning("SKIPPED") + " - Java version " + e.javaVersion + " not in range "
+ e.supportedRange);
return;
} catch (UnsupportedMavenVersionException e) {
out.println(AnsiSupport.warning("SKIPPED") + " - Maven version " + e.mavenVersion + " not in range "
+ e.supportedRange);
return;
} catch (BrokenMavenVersionException e) {
out.println(AnsiSupport.error("UNEXPECTED OK") + " - Maven version " + e.mavenVersion + " expected to fail "
+ formatTime(milliseconds));
fail("Expected failure when with Maven version " + e.mavenVersion);
} catch (Throwable t) {
milliseconds = System.currentTimeMillis() - milliseconds;
if (invert != null) {
out.println(AnsiSupport.success("EXPECTED FAIL") + " - Maven version " + invert.mavenVersion
+ " expected to fail " + formatTime(milliseconds));
} else {
out.println(AnsiSupport.error("FAILURE") + " " + formatTime(milliseconds));
throw t;
}
}
}
/**
* Guards the execution of a test case by checking that the current Java version matches the specified version
* range. If the check fails, an exception will be thrown which aborts the current test and marks it as skipped. One
* would usually call this method right at the start of a test method.
*
* @param versionRange The version range that specifies the acceptable Java versions for the test, must not be
* <code>null</code>.
*/
protected void requiresJavaVersion(String versionRange) {
VersionRange range;
try {
range = VersionRange.createFromVersionSpec(versionRange);
} catch (InvalidVersionSpecificationException e) {
throw (RuntimeException) new IllegalArgumentException("Invalid version range: " + versionRange, e);
}
ArtifactVersion version = getJavaVersion();
if (!range.containsVersion(version)) {
throw new UnsupportedJavaVersionException(version, range);
}
}
/**
* Guards the execution of a test case by checking that the current Maven version matches the specified version
* range. If the check fails, an exception will be thrown which aborts the current test and marks it as skipped. One
* would usually call this method right at the start of a test method.
*
* @param versionRange The version range that specifies the acceptable Maven versions for the test, must not be
* <code>null</code>.
*/
protected void requiresMavenVersion(String versionRange) {
VersionRange range;
try {
range = VersionRange.createFromVersionSpec(versionRange);
} catch (InvalidVersionSpecificationException e) {
throw (RuntimeException) new IllegalArgumentException("Invalid version range: " + versionRange, e);
}
ArtifactVersion version = getMavenVersion();
if (version != null) {
if (!range.containsVersion(removePattern(version))) {
throw new UnsupportedMavenVersionException(version, range);
}
} else {
out.println("WARNING: " + getITName() + ": version range '" + versionRange
+ "' supplied but no Maven version found - not skipping test.");
}
}
/**
* Inverts the execution of a test case for cases where we discovered a bug in the test case, have corrected the
* test case and shipped versions of Maven with a bug because of the faulty test case. This method allows the
* tests to continue passing against the historical releases as they historically would, as well as verifying that
* the test is no longer providing a false positive.
*
* @param versionRange
*/
protected void failingMavenVersions(String versionRange) {
assertNull("Only call failingMavenVersions at most once per test", invert);
VersionRange range;
try {
range = VersionRange.createFromVersionSpec(versionRange);
} catch (InvalidVersionSpecificationException e) {
throw (RuntimeException) new IllegalArgumentException("Invalid version range: " + versionRange, e);
}
ArtifactVersion version = getMavenVersion();
if (version != null) {
if (range.containsVersion(removePattern(version))) {
invert = new BrokenMavenVersionException(version, range);
}
} else {
out.println("WARNING: " + getITName() + ": version range '" + versionRange
+ "' supplied but no Maven version found - not marking test as expected to fail.");
}
}
private static class NonCloseableInputStream extends FilterInputStream {
NonCloseableInputStream(InputStream delegate) {
super(delegate);
}
@Override
public void close() throws IOException {}
}
private class UnsupportedJavaVersionException extends TestAbortedException {
@SuppressWarnings("checkstyle:visibilitymodifier")
public ArtifactVersion javaVersion;
@SuppressWarnings("checkstyle:visibilitymodifier")
public VersionRange supportedRange;
private UnsupportedJavaVersionException(ArtifactVersion javaVersion, VersionRange supportedRange) {
super("Java version " + javaVersion + " not in range " + supportedRange);
this.javaVersion = javaVersion;
this.supportedRange = supportedRange;
}
}
private class UnsupportedMavenVersionException extends TestAbortedException {
@SuppressWarnings("checkstyle:visibilitymodifier")
public ArtifactVersion mavenVersion;
@SuppressWarnings("checkstyle:visibilitymodifier")
public VersionRange supportedRange;
private UnsupportedMavenVersionException(ArtifactVersion mavenVersion, VersionRange supportedRange) {
super("Maven version " + mavenVersion + " not in range " + supportedRange);
this.mavenVersion = mavenVersion;
this.supportedRange = supportedRange;
}
}
private class BrokenMavenVersionException extends RuntimeException {
@SuppressWarnings("checkstyle:visibilitymodifier")
public ArtifactVersion mavenVersion;
@SuppressWarnings("checkstyle:visibilitymodifier")
public VersionRange supportedRange;
private BrokenMavenVersionException(ArtifactVersion mavenVersion, VersionRange supportedRange) {
this.mavenVersion = mavenVersion;
this.supportedRange = supportedRange;
}
}
private String getITName() {
String simpleName = getClass().getName();
int idx = simpleName.lastIndexOf('.');
simpleName = idx >= 0 ? simpleName.substring(idx + 1) : simpleName;
simpleName = simpleName.startsWith("MavenIT") ? simpleName.substring("MavenIT".length()) : simpleName;
simpleName = simpleName.endsWith("Test") ? simpleName.substring(0, simpleName.length() - 4) : simpleName;
return simpleName;
}
private String getTestName() {
String className = getITName();
String methodName = getName();
if (methodName.startsWith("test")) {
methodName = methodName.substring(4);
}
return className + '.' + methodName + "()";
}
private String pad(int chars) {
StringBuilder buffer = new StringBuilder(128);
for (int i = 0; i < chars; i++) {
buffer.append('.');
}
return buffer.toString();
}
private String formatTime(long milliseconds) {
return SECS_FORMAT.format(milliseconds / 1000.0);
}
protected File setupLocalRepo() throws IOException {
String tempDirPath = System.getProperty("maven.it.tmpdir", System.getProperty("java.io.tmpdir"));
File localRepo = new File(tempDirPath, "local-repository/" + getITName());
if (localRepo.isDirectory()) {
FileUtils.deleteDirectory(localRepo);
}
System.setProperty("maven.repo.local", localRepo.getAbsolutePath());
return localRepo;
}
ArtifactVersion removePattern(ArtifactVersion version) {
String v = version.toString();
Matcher m = Pattern.compile(matchPattern).matcher(v);
if (m.matches()) {
return new DefaultArtifactVersion(m.group(1));
}
return version;
}
protected Verifier newVerifier(String basedir) throws VerificationException {
return newVerifier(basedir, false);
}
protected Verifier newVerifier(String basedir, String settings) throws VerificationException {
return newVerifier(basedir, settings, false);
}
protected Verifier newVerifier(String basedir, boolean debug) throws VerificationException {
return newVerifier(basedir, "", debug);
}
protected Verifier newVerifier(String basedir, String settings, boolean debug) throws VerificationException {
Verifier verifier = new Verifier(basedir, debug);
verifier.setAutoclean(false);
if (settings != null) {
File settingsFile;
if (settings.length() > 0) {
settingsFile = new File("settings-" + settings + ".xml");
} else {
settingsFile = new File("settings.xml");
}
if (!settingsFile.isAbsolute()) {
String settingsDir = System.getProperty("maven.it.global-settings.dir", "");
if (settingsDir.length() > 0) {
settingsFile = new File(settingsDir, settingsFile.getPath());
} else {
//
// Make is easier to run ITs from m2e in Maven IT mode without having to set any additional
// properties.
//
settingsFile = new File("target/test-classes", settingsFile.getPath());
}
}
String path = settingsFile.getAbsolutePath();
// dedicated CLI option only available since MNG-3914
if (matchesVersionRange("[2.1.0,)")) {
verifier.addCliArgument("--global-settings");
if (path.indexOf(' ') < 0) {
verifier.addCliArgument(path);
} else {
verifier.addCliArgument('"' + path + '"');
}
} else {
verifier.getSystemProperties().put("org.apache.maven.global-settings", path);
}
}
try {
// Java7 TLS protocol
if (VersionRange.createFromVersionSpec("(,1.8.0)").containsVersion(getJavaVersion())) {
verifier.addCliArgument("-Dhttps.protocols=TLSv1.2");
}
// auto set source+target to lowest reasonable java version
// Java9 requires at least 1.6
if (VersionRange.createFromVersionSpec("[9,12)").containsVersion(getJavaVersion())) {
verifier.getSystemProperties().put("maven.compiler.source", "1.8");
verifier.getSystemProperties().put("maven.compiler.target", "1.8");
verifier.getSystemProperties().put("maven.compiler.release", "8");
}
// Java12 requires at least 7
if (VersionRange.createFromVersionSpec("[12,)").containsVersion(getJavaVersion())) {
verifier.getSystemProperties().put("maven.compiler.source", "8");
verifier.getSystemProperties().put("maven.compiler.target", "8");
verifier.getSystemProperties().put("maven.compiler.release", "8");
}
} catch (InvalidVersionSpecificationException e) {
// noop
}
return verifier;
}
@BeforeEach
void setupContext(TestInfo testInfo) {
testName = testInfo.getTestMethod().get().getName();
}
protected String getName() {
return testName;
}
public static void assertCanonicalFileEquals(String message, File expected, File actual) throws IOException {
assertEquals(message, expected.getCanonicalFile(), actual.getCanonicalFile());
}
public static void assertCanonicalFileEquals(File expected, File actual) throws IOException {
assertCanonicalFileEquals(null, expected, actual);
}
public static void assertCanonicalFileEquals(String message, String expected, String actual) throws IOException {
assertCanonicalFileEquals(message, new File(expected), new File(actual));
}
public static void assertCanonicalFileEquals(String expected, String actual) throws IOException {
assertCanonicalFileEquals(null, new File(expected), new File(actual));
}
public static void assertEquals(Object o1, Object o2) {
assertEquals(null, o1, o2);
}
public static void assertEquals(String message, Object o1, Object o2) {
org.junit.jupiter.api.Assertions.assertEquals(o1, o2, message);
}
public static void assertNotEquals(Object o1, Object o2) {
assertNotEquals(null, o1, o2);
}
public static void assertNotEquals(String message, Object o1, Object o2) {
org.junit.jupiter.api.Assertions.assertNotEquals(o1, o2, message);
}
public static void assertTrue(boolean test) {
assertTrue(null, test);
}
public static void assertTrue(String message, boolean test) {
org.junit.jupiter.api.Assertions.assertTrue(test, message);
}
public static void assertFalse(boolean test) {
assertFalse(null, test);
}
public static void assertFalse(String message, boolean test) {
org.junit.jupiter.api.Assertions.assertFalse(test, message);
}
public static void assertNotNull(Object o) {
assertNotNull(null, o);
}
public static void assertNotNull(String message, Object o) {
org.junit.jupiter.api.Assertions.assertNotNull(o, message);
}
public static void assertNull(Object o) {
assertNull(null, o);
}
public static void assertNull(String message, Object o) {
org.junit.jupiter.api.Assertions.assertNull(o, message);
}
public static void fail(String message) {
org.junit.jupiter.api.Assertions.fail(message);
}
}