rake-runner-test/src/jetbrains/slow/plugins/rakerunner/AbstractRakeRunnerTest.java (423 lines of code) (raw):
package jetbrains.slow.plugins.rakerunner;
import com.intellij.openapi.util.SystemInfo;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;
import jetbrains.buildServer.PartialBuildMessagesChecker;
import jetbrains.buildServer.RunnerTest2Base;
import jetbrains.buildServer.agent.AgentRuntimeProperties;
import jetbrains.buildServer.agent.FlowLogger;
import jetbrains.buildServer.agent.ServerProvidedProperties;
import jetbrains.buildServer.agent.rakerunner.SupportedTestFramework;
import jetbrains.buildServer.log.LogInitializer;
import jetbrains.buildServer.messages.BuildMessage1;
import jetbrains.buildServer.messages.BuildMessagesProcessor;
import jetbrains.buildServer.rakerunner.RakeRunnerConstants;
import jetbrains.buildServer.serverSide.RunningBuildEx;
import jetbrains.buildServer.serverSide.SBuildServer;
import jetbrains.buildServer.serverSide.ShortStatistics;
import jetbrains.buildServer.serverSide.SimpleParameter;
import jetbrains.buildServer.serverSide.buildLog.LogMessage;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.util.StringUtil;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.testng.Assert;
import org.testng.ITest;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import static jetbrains.buildServer.messages.serviceMessages.ServiceMessage.SERVICE_MESSAGE_START;
import static jetbrains.slow.plugins.rakerunner.MockingOptions.*;
/**
* @author Roman Chernyatchik
*/
public abstract class AbstractRakeRunnerTest extends RunnerTest2Base implements ITest {
static {
LogInitializer.reconfigureLog4j((loggerContext, configuration) -> {
PatternLayout patternLayout = PatternLayout.newBuilder().withPattern(PatternLayout.TTCC_CONVERSION_PATTERN).build();
ConsoleAppender consoleAppender = ConsoleAppender.newBuilder().setName("console").setLayout(patternLayout).build();
String name = "jetbrains.slow.plugins.rakerunner";
LoggerConfig loggerConfig = new LoggerConfig(name, Level.WARN, false);
loggerConfig.addAppender(consoleAppender, null, null);
configuration.addLogger(name, loggerConfig);
});
}
//private MockingOptions[] myCheckerMockOptions = new MockingOptions[0];
private boolean myShouldTranslateMessages = false;
private String myRubyVersion;
private final Set<String> myFilesToDelete = new HashSet<String>();
private static File ourTempsContainerDir;
private File myWorkingDirectory;
protected AbstractRakeRunnerTest() {
}
@Override
@NotNull
protected String getRunnerType() {
return RakeRunnerConstants.RUNNER_TYPE;
}
@BeforeMethod
@Override
public void setUp1() throws Throwable {
setAgentOwnPort();
super.setUp1();
setMockingOptions(FAKE_STACK_TRACE, FAKE_LOCATION_URL, FAKE_ERROR_MSG);
setMessagesTranslationEnabled(false);
if (myRubyVersion == null) {
if (SystemInfo.isWindows) {
setInterpreterPath();
} else if (SystemInfo.isUnix) {
setRubyConfiguration();
}
} else {
if (SystemInfo.isWindows) {
setInterpreterPath(myRubyVersion);
} else if (SystemInfo.isUnix) {
setRubyConfiguration(myRubyVersion);
}
}
getBuildType().addRunParameter(
new SimpleParameter(RakeRunnerConstants.SERVER_CONFIGURATION_VERSION_PROPERTY, RakeRunnerConstants.CURRENT_CONFIG_VERSION));
getBuildType().addRunParameter(
new SimpleParameter(ServerProvidedProperties.TEAMCITY_VERSION_ENV, "2020.2"));
}
private static int ourAgentOwnPort = 0;
private static void setAgentOwnPort() {
if (ourAgentOwnPort != 0) {
return;
}
String property = System.getProperty(AgentRuntimeProperties.OWN_PORT);
if (property == null) {
ourAgentOwnPort = 9090;
}
try {
ourAgentOwnPort = Integer.parseInt(property);
if (ourAgentOwnPort == 12345) ourAgentOwnPort = 9090;
} catch (NumberFormatException e) {
ourAgentOwnPort = 9090;
}
}
public static int getAgentOwnPort() {
if (ourAgentOwnPort == 0) {
new Throwable("AbstractRakeRunnerTest.getAgentOwnPort called to early").printStackTrace(System.err);
return 9090;
}
return ourAgentOwnPort;
}
protected void setRubyVersion(@NotNull final String rubyVersion) {
myRubyVersion = rubyVersion;
}
protected String getRubyVersion() {
return myRubyVersion;
}
protected void setMessagesTranslationEnabled(boolean enabled) {
System.getProperties().remove(BuildMessagesProcessor.TEAMCITY_BUILD_MESSAGES_TRANSLATION_ENABLED_PROP);
if (!enabled) {
System.setProperty(BuildMessagesProcessor.TEAMCITY_BUILD_MESSAGES_TRANSLATION_ENABLED_PROP, "false");
}
myShouldTranslateMessages = enabled;
}
private void setInterpreterPath() throws RakeRunnerTestUtil.InterpreterNotFoundException {
RakeRunnerTestUtil.setInterpreterPath(getBuildType());
}
private void setInterpreterPath(@NotNull final String rubyVersion) throws RakeRunnerTestUtil.InterpreterNotFoundException {
RakeRunnerTestUtil.setInterpreterPath(getBuildType(), rubyVersion);
}
private void setRubyConfiguration() {
if (RakeRunnerTestUtil.isUseRVM()) {
RakeRunnerTestUtil.setRVMConfiguration(getBuildType());
} else if (RakeRunnerTestUtil.isUseRbEnv()) {
RakeRunnerTestUtil.setRbEnvConfiguration(getBuildType());
}
}
private void setRubyConfiguration(@NotNull final String rubySdkName) {
if (RakeRunnerTestUtil.isUseRVM()) {
RakeRunnerTestUtil.setRVMConfiguration(getBuildType(), rubySdkName);
} else if (RakeRunnerTestUtil.isUseRbEnv()) {
RakeRunnerTestUtil.setRbEnvConfiguration(getBuildType(), rubySdkName);
}
}
protected void useRVMRubySDK(@NotNull String sdkname) {
RakeRunnerTestUtil.useRVMRubySDK(sdkname, getBuildType());
}
protected void useRVMGemSet(@NotNull String gemset) {
RakeRunnerTestUtil.useRVMGemSet(gemset, getBuildType());
}
protected void setUseBundle(final boolean use) {
RakeRunnerTestUtil.useBundleExec(getBuildType(), use);
}
@Override
protected File getTestDataPath(final String buildFileName) {
return RakeRunnerTestUtil.getTestDataItemPath(getTestDataSuffixPath() + buildFileName);
}
@Override
protected String getTestDataSuffixPath() {
return "plugins/rakeRunner/";
}
public static File getTempsContainerDir() throws IOException {
if (ourTempsContainerDir == null) {
synchronized (AbstractRakeRunnerTest.class) {
if (ourTempsContainerDir == null) {
ourTempsContainerDir = FileUtil.createTempDirectory("rake-runner-temp-container", null);
//ourTempsContainerDir = FileUtil.createEmptyDir(RakeRunnerTestUtil.getTestDataItemPath("temp-container"));
}
}
}
return ourTempsContainerDir;
}
protected void setTaskNames(final String task_names) {
addRunParameter(RakeRunnerConstants.SERVER_UI_RAKE_TASKS_PROPERTY, task_names);
}
protected void initAndDoTest(final String task_full_name,
final boolean shouldPass,
final String testDataApp) throws Throwable {
initAndDoTest(task_full_name, "", shouldPass, testDataApp);
}
protected void initAndDoRealTest(final String task_full_name,
final boolean shouldPass,
final String testDataApp) throws Throwable {
initAndDoTest(task_full_name, "_real", shouldPass, testDataApp);
}
protected void doTestWithoutLogCheck(final String task_full_name,
final boolean shouldPass,
final String testDataApp) throws Throwable {
initAndDoTest(task_full_name, null, shouldPass, testDataApp);
}
protected void initAndDoTest(final String task_full_name,
@Nullable final String result_file_suffix,
final boolean shouldPass,
final String testDataApp) throws Throwable {
final File workingDirectory = getTestDataPath(testDataApp).getAbsoluteFile();
initAndDoTest(task_full_name, result_file_suffix, shouldPass, testDataApp, workingDirectory);
}
protected void initAndDoTest(final String task_full_name,
@Nullable final String result_file_suffix,
boolean shouldPass,
@NotNull final String testDataApp,
@NotNull final File workingDirectory) throws Throwable {
myWorkingDirectory = workingDirectory;
addRunParameter(AgentRuntimeProperties.BUILD_WORKING_DIR, workingDirectory.getAbsolutePath());
// addRunParameter(AgentRuntimeProperties.BUILD_CHECKOUT_DIR, workingDirectory.getAbsolutePath());
setTaskNames(task_full_name);
final String resultFileName = result_file_suffix == null
? null
: testDataApp + "/results/"
+ task_full_name.replace(":", "/")
+ result_file_suffix
// lets automatically expect "_log"
// suffix to each translated result (build log) file
+ (myShouldTranslateMessages ? "_log" : "");
doTest(resultFileName);
final List<LogMessage> errorMessages = getLastFinishedBuild().getBuildLog().getErrorMessages();
assertEquals(errorMessages.toString(), shouldPass, !getLastFinishedBuild().getBuildStatus().isFailed());
}
@AfterMethod(alwaysRun = true)
public void removeBundleFiles() throws Throwable {
for (final String path : myFilesToDelete) {
FileUtil.delete(new File(path));
}
}
protected void rakeUI_EnableTraceOption() {
addRunParameter(RakeRunnerConstants.SERVER_UI_RAKE_TRACE_INVOKE_EXEC_STAGES_ENABLED, "true");
}
protected void setMockingOptions(final MockingOptions... options) {
setBuildEnvironmentVariable(getEnvVarName(), getEnvVarValue(options));
}
@Override
protected void setPartialMessagesChecker() {
setMessageChecker(new PartialBuildMessagesChecker() {
// (all except ' and |) or |' or |n or |r or || or |]
//private final String VALUE_PATTERN = "'(([^'|]||\\|'||\\|n||\\|r||\\|\\|||\\|\\])+)'";
//private final Pattern TIMESTAMP_VALUE_PATTERN = Pattern.compile(" timestamp=" + VALUE_PATTERN);
//private final Pattern ERROR_DETAILS_VALUE_PATTERN = Pattern.compile(" errorDetails=" + VALUE_PATTERN);
//private final Pattern MESSAGE_TEXT_PATTERN = Pattern.compile("message text=" + VALUE_PATTERN);
//private final Pattern LOCATION_PATTERN = Pattern.compile("location=" + VALUE_PATTERN);
private final Pattern VFS_FILE_PROTOCOL_PATTERN_WIN = Pattern.compile("file://");
@Override
public void assertMessagesEquals(final File file,
final String actual) throws Throwable {
//final String patchedActual = mockMessageText(mockErrorDetails(mockTimeStamp(actual)));
String patchedActual = actual.trim();
if (SystemInfo.isWindows) {
patchedActual = VFS_FILE_PROTOCOL_PATTERN_WIN.matcher(actual).replaceAll("file:");
}
patchedActual = patchedActual.replaceAll(" +", " ");
// patchedActual = patchedActual.replace("RSpec", "Spec");
patchedActual = reorderAttributesOfServiceMessages(patchedActual);
//for (MockingOptions option : myCheckerMockOptions) {
// switch (option) {
// case FAKE_ERROR_MSG:
// patchedActual = mockErrorDetails(patchedActual);
// break;
// case FAKE_LOCATION_URL:
// patchedActual = mockLocation(patchedActual);
// break;
// case FAKE_STACK_TRACE:
// patchedActual = mockErrorDetails(patchedActual);
// break;
// case FAKE_TIME:
// patchedActual = mockTimeStamp(patchedActual);
// break;
// }
//}
super.assertMessagesEquals(file, patchedActual);
}
//private String mockErrorDetails(final String text) {
// return ERROR_DETAILS_VALUE_PATTERN.matcher(text).replaceAll(" errorDetails='##STACK_TRACE##'");
//}
//
//private String mockTimeStamp(String actual) {
// return TIMESTAMP_VALUE_PATTERN.matcher(actual).replaceAll(" timestamp='##TIME##'");
//}
//
//private String mockMessageText(String actual) {
// return MESSAGE_TEXT_PATTERN.matcher(actual).replaceAll("message text='##MESSAGE##'");
//}
//
//private String mockLocation(String actual) {
// return LOCATION_PATTERN.matcher(actual).replaceAll("location='$LOCATION$'");
//}
});
}
private static String reorderAttributesOfServiceMessages(String patchedActual) {
final String[] lines = patchedActual.split("\n");
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
final int serviceMessageStart = line.indexOf(SERVICE_MESSAGE_START);
if (serviceMessageStart != -1) {
// Fix order of attributes to match order of attributes in the test data.
// Just to avoid patching all the test data.
String text = line.substring(serviceMessageStart + SERVICE_MESSAGE_START.length());
String prefix = line.substring(0, serviceMessageStart + SERVICE_MESSAGE_START.length() + text.indexOf(" ") + 1);
text = text.substring(text.indexOf(" "), text.lastIndexOf(']')).trim();
if (text.startsWith("'")) {
// In case we have 'Single attribute message'
// do nothing
} else {
// In case we located at least one attribute ('Multiple attribute message')
try {
final Map<String, String> attributes = StringUtil.stringToProperties(text, StringUtil.STD_ESCAPER2, false);
removeAttributes(attributes);
final ArrayList<String> keys = new ArrayList<String>(attributes.keySet());
reorderAttributes(keys);
final Map<String, String> orderedAttributes = new LinkedHashMap<String, String>();
for (String key : keys) {
orderedAttributes.put(key, attributes.get(key));
}
final String s = StringUtil.propertiesToString(orderedAttributes, StringUtil.STD_ESCAPER2);
String newline = prefix + s + line.substring(line.lastIndexOf(']'));
lines[i] = newline;
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
}
patchedActual = StringUtil.join("\n", lines);
return patchedActual;
}
private static void removeAttributes(Map<String, String> attributes) {
for(String trimed: Arrays.asList("nodeId", "parentNodeId")) {
attributes.remove(trimed);
}
}
private static void reorderAttributes(final List<String> foundAttrs) {
final String[] sequence = {"name", "captureStandardOutput", "locationHint", "message", "details", "error",
"text", "status", "errorDetails", "type", "duration", "timestamp"
};
int ii = 0;
for (String attrName : sequence) {
for (int j = ii; j < foundAttrs.size(); j++) {
if (foundAttrs.get(j).equals(attrName)) {
swap(foundAttrs, ii, j);
ii++;
break;
}
}
}
}
private static void swap(final List<String> foundAttrs, final int i, final int j) {
if (j == i) return;
String ival = foundAttrs.get(i);
foundAttrs.set(i, foundAttrs.get(j));
foundAttrs.set(j, ival);
}
protected void assertTestsCount(int succ, int failed, int ignored) {
assertTestsCount(succ, failed, ignored, getLastFinishedBuild().getShortStatistics());
}
protected void assertTestsCount(int succ, int failed, int ignored, ShortStatistics shortStatistics) {
final int aSucc = shortStatistics.getPassedTestCount();
final int aFailed = shortStatistics.getFailedTestCount();
final int aIgnored = shortStatistics.getIgnoredTestCount();
try {
Assert.assertEquals(aSucc, succ, "success");
Assert.assertEquals(aFailed, failed, "failed");
Assert.assertEquals(aIgnored, ignored, "ignored");
} catch (Throwable e) {
System.out.println("aSucc = " + aSucc);
System.out.println("aFailed = " + aFailed);
System.out.println("aIgnored = " + aIgnored);
throw new RuntimeException(e);
}
}
protected void activateTestFramework(@NotNull final SupportedTestFramework... frameworks) {
for (SupportedTestFramework framework : frameworks) {
activateTestFramework(framework);
}
}
protected void activateTestFramework(@NotNull final SupportedTestFramework framework) {
getBuildType().addRunParameter(new SimpleParameter(framework.getFrameworkUIProperty(), "true"));
}
@Override
protected String doRunnerSpecificReplacement(final String expected) {
String msg = expected.replaceAll("[0-9]{4}-[0-9]{2}-[0-9]{2}('T'|T)[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}[+\\-]{1}[0-9]+", "##TIME##");
msg = msg.replaceAll("duration ?= ?'[0-9]+'", "duration='##DURATION##'");
msg = msg.replaceAll("duration ?= ?'[0-9]*\\Q##OWN_PORT##\\E[0-9]*'", "duration='##DURATION##'");
if (myWorkingDirectory != null) {
final String rel = FileUtil.getRelativePath(getCurrentDir().getAbsoluteFile(), myWorkingDirectory.getAbsoluteFile());
if (rel != null) {
if (rel.startsWith("..")) {
msg = replacePath(msg, myWorkingDirectory.getAbsoluteFile(), "##WORKING_DIR##");
} else {
msg = msg.replaceAll(Pattern.quote(rel), "##WORKING_DIR##");
}
}
}
return msg;
}
protected void doPrepareGemset(@NotNull final String version, @NotNull final String gemset, @NotNull final Logger LOG, @NotNull final File gemfile) throws IOException {
if (!SystemInfo.isUnix) {
return;
}
final File cacheDir = getTestDataPath("gems/vendor/cache");
FileUtil.createDir(cacheDir);
final File workingDirectory = gemfile.getParentFile();
final List<String> commands = new ArrayList<String>();
if (RakeRunnerTestUtil.isUseRVM()) {
commands.add("source " + getTestDataPath("gems/checkRVMCommand.sh").getAbsolutePath());
commands.add("checkRVMCommand");
commands.add(String.format("rvm use \"%s@%s\" --create", version, gemset));
} else if (RakeRunnerTestUtil.isUseRbEnv()) {
commands.add("rbenv local " + version);
} else {
throw new IllegalStateException("Expected to be run on machine with either RVM or RbEnv installed.");
}
Collections.addAll(commands,
"[[ ! -d 'vendor' ]] && mkdir vendor",
"[[ ! -d 'vendor/cache' ]] && ln -s '" + cacheDir.getAbsolutePath() + "' 'vendor/cache'",
"gem which bundler || gem install bundler",
"rm -f Gemfile.lock",
"bundle install --local --no-prune || (rm -f Gemfile.lock; bundle install --no-prune)",
"bundle update",
"bundle package --no-prune");
RunCommandsHelper.runBashScript(LOG, workingDirectory, commands.toArray(new String[commands.size()]));
//FileUtil.copyDir(localCacheDir, cacheDir);
try {
final FlowLogger fl = LogUtil.getFlowLogger(LOG);
fl.activityStarted("Actual Gemfile.lock:", "AGL");
fl.message(FileUtil.readText(new File(gemfile.getParent(), "Gemfile.lock")));
fl.activityFinished("Actual Gemfile.lock:", "AGL");
} catch (Exception ignored) {
}
}
protected void doInstallBundlerGem(@NotNull final Logger LOG) throws IOException {
if (!SystemInfo.isUnix) {
return;
}
final String sdk = getRunnerParameter(RakeRunnerConstants.SERVER_UI_RUBY_RVM_SDK_NAME);
final String gs = getRunnerParameter(RakeRunnerConstants.SERVER_UI_RUBY_RVM_GEMSET_NAME);
if (RakeRunnerTestUtil.isUseRVM()) {
RunCommandsHelper.runBashScript(LOG, getTestDataPath("gems/checkRVMCommand.sh").getParentFile(),
"source " + getTestDataPath("gems/checkRVMCommand.sh").getAbsolutePath(),
"checkRVMCommand",
StringUtil.isEmptyOrSpaces(gs) ? String.format("rvm use \"%s\"", sdk) : String.format("rvm use \"%s\" --create", sdk + "@" + gs),
"gem which bundler || gem install bundler"
);
} else if (RakeRunnerTestUtil.isUseRbEnv()) {
RunCommandsHelper.runBashScript(LOG, getTempsContainerDir(),
"rbenv shell " + sdk,
"gem which bundler || gem install bundler"
);
} else {
throw new IllegalStateException("Expected to be run on machine with either RVM or RbEnv installed.");
}
}
public String getTestName() {
final String base = getClass().getName();
final List<String> parametersList = getTestNameParametersList();
if (parametersList.isEmpty()) {
return base;
}
return base + "(" + StringUtil.join(",", parametersList) + ")";
}
@NotNull
protected List<String> getTestNameParametersList() {
if (myRubyVersion == null) {
return new ArrayList<String>();
}
return new ArrayList<String>(Arrays.asList(myRubyVersion.replaceAll("\\-p\\d+", "")));
}
/**
* BuildMessagesProcessor which do nothing
*/
private static class AsIsBuildMessagesProcessor extends BuildMessagesProcessor {
public AsIsBuildMessagesProcessor(SBuildServer server) {
super(server);
}
@NotNull
@Override
public List<BuildMessage1> translateMessages(@NotNull final List<BuildMessage1> initial, @NotNull final RunningBuildEx runningBuild) {
return initial;
}
}
}