com.liferay.jenkins.results.parser.AutoCloseUtil Maven / Gradle / Ivy
Show all versions of com.liferay.jenkins.results.parser
/**
* SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/
package com.liferay.jenkins.results.parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
/**
* @author Peter Yoo
*/
public class AutoCloseUtil {
public static boolean debug = false;
public static void autoClose(PullRequest pullRequest, Build build)
throws Exception {
if (pullRequest.isAutoCloseCommentAvailable()) {
return;
}
String gitHubReceiverUsername = pullRequest.getOwnerUsername();
String gitHubSenderUsername = pullRequest.getSenderUsername();
if ((gitHubReceiverUsername == null) ||
(gitHubSenderUsername == null) ||
!_autoCloseReceiverUsernames.contains(gitHubReceiverUsername) ||
gitHubReceiverUsername.equals(gitHubSenderUsername)) {
return;
}
pullRequest.close();
StringBuilder sb = new StringBuilder();
sb.append("The pull request tester is still running.
");
sb.append("Please wait until you get the ");
sb.append("final report before running 'ci:retest'.");
sb.append("
See this link to check on the status of your ");
sb.append("test:
");
sb.append("@");
sb.append(pullRequest.getSenderUsername());
sb.append("
");
sb.append("However, the pull request was closed.
");
sb.append("The pull request was closed because the following ");
sb.append("critical builds had failed:
For information as to why we ");
sb.append("automatically close out certain pull requests see this ");
sb.append("article.
*");
}
else {
sb.append(" auto-close=\"false\">*This pull will ");
sb.append("no longer automatically close if this comment is ");
sb.append("available. ");
}
sb.append("If you believe this is a mistake please reopen this ");
sb.append("pull by entering the following command as a comment.");
sb.append("ci:reopen
");
if (sourceFormatBuild) {
sb.append("*The reopened pull request may ");
sb.append("be automatically closed again if other critical ");
sb.append("batches or tests fail.");
}
sb.append("
Critical Failure Details:
");
try {
sb.append(Dom4JUtil.format(build.getGitHubMessageElement(), false));
}
catch (Exception exception) {
exception.printStackTrace();
throw exception;
}
if (!_autoCloseGitHubCommentMentionUsernames.isEmpty()) {
sb.append("cc");
for (String autoCloseGitHubCommentMentionUsername :
_autoCloseGitHubCommentMentionUsernames) {
sb.append(" @");
sb.append(autoCloseGitHubCommentMentionUsername);
}
sb.append("");
}
pullRequest.addComment(sb.toString());
}
public static boolean autoCloseOnCriticalBatchFailures(
PullRequest pullRequest, Build topLevelBuild)
throws Exception {
if (pullRequest.isAutoCloseCommentAvailable()) {
return false;
}
String gitHubReceiverUsername = pullRequest.getOwnerUsername();
String gitHubSenderUsername = pullRequest.getSenderUsername();
if ((gitHubReceiverUsername == null) ||
(gitHubSenderUsername == null) ||
!_autoCloseReceiverUsernames.contains(gitHubReceiverUsername) ||
gitHubReceiverUsername.equals(gitHubSenderUsername)) {
return false;
}
List autoCloseRules = getAutoCloseRules(pullRequest);
for (AutoCloseRule autoCloseRule : autoCloseRules) {
List downstreamBuilds = new ArrayList<>();
if (topLevelBuild instanceof ParentBuild) {
ParentBuild parentBuild = (ParentBuild)topLevelBuild;
downstreamBuilds.addAll(parentBuild.getDownstreamBuilds(null));
}
if (downstreamBuilds.isEmpty()) {
downstreamBuilds = new ArrayList<>();
downstreamBuilds.add(topLevelBuild);
}
List failedDownstreamBuilds = autoCloseRule.evaluate(
downstreamBuilds);
if (failedDownstreamBuilds.isEmpty()) {
continue;
}
pullRequest.close();
StringBuilder sb = new StringBuilder();
sb.append("The pull request tester is still running.
");
sb.append("Please wait until you get the ");
sb.append("final report before running 'ci:retest'.");
sb.append("
See this link to check on the status of your ");
sb.append("test:
");
sb.append("@");
sb.append(gitHubSenderUsername);
sb.append("
");
sb.append("However, the pull request was closed.
");
sb.append("The pull request was closed because the following ");
sb.append("critical batches had failed:
");
String failureBuildURL = "";
for (Build failedDownstreamBuild : failedDownstreamBuilds) {
failureBuildURL = failedDownstreamBuild.getBuildURL();
sb.append("- ");
String jobVariant = failedDownstreamBuild.getJobVariant();
if ((jobVariant != null) && !jobVariant.isEmpty()) {
sb.append(jobVariant);
}
else {
sb.append(failedDownstreamBuild.getJobName());
}
sb.append("
");
}
sb.append("
For information as to why we automatically ");
sb.append("close out certain pull requests see this ");
sb.append("article.
*");
}
else {
sb.append(" auto-close=\"false\">*This pull will ");
sb.append("no longer automatically close if this comment is ");
sb.append("available. ");
}
sb.append("If you believe this is a mistake please reopen this ");
sb.append("pull by entering the following command as a comment.");
sb.append("ci:reopen
");
if (sourceFormatBuild) {
sb.append("*The reopened pull request may ");
sb.append("be automatically closed again if other critical ");
sb.append("batches or tests fail.");
}
sb.append("
Critical Failure Details:
");
for (Build failedDownstreamBuild : failedDownstreamBuilds) {
try {
sb.append(
Dom4JUtil.format(
failedDownstreamBuild.getGitHubMessageElement(),
false));
}
catch (Exception exception) {
exception.printStackTrace();
throw exception;
}
}
if (!_autoCloseGitHubCommentMentionUsernames.isEmpty()) {
sb.append("cc");
for (String autoCloseGitHubCommentMentionUsername :
_autoCloseGitHubCommentMentionUsernames) {
sb.append(" @");
sb.append(autoCloseGitHubCommentMentionUsername);
}
sb.append("");
}
pullRequest.addComment(sb.toString());
return true;
}
return false;
}
public static boolean autoCloseOnCriticalTestFailures(
PullRequest pullRequest, Build topLevelBuild)
throws Exception {
if (pullRequest.isAutoCloseCommentAvailable() ||
!isAutoCloseOnCriticalTestFailuresActive(pullRequest)) {
return false;
}
String gitHubReceiverUsername = pullRequest.getOwnerUsername();
String gitHubSenderUsername = pullRequest.getSenderUsername();
if ((gitHubReceiverUsername == null) ||
(gitHubSenderUsername == null) ||
!_autoCloseReceiverUsernames.contains(gitHubReceiverUsername) ||
gitHubReceiverUsername.equals(gitHubSenderUsername)) {
return false;
}
Build failedDownstreamBuild = null;
List jenkinsJobFailureURLs = new ArrayList<>();
List downstreamBuilds = new ArrayList<>();
if (topLevelBuild instanceof ParentBuild) {
ParentBuild parentBuild = (ParentBuild)topLevelBuild;
downstreamBuilds.addAll(parentBuild.getDownstreamBuilds(null));
}
Properties localLiferayJenkinsEEBuildProperties =
JenkinsResultsParserUtil.getLocalLiferayJenkinsEEBuildProperties();
for (Build downstreamBuild : downstreamBuilds) {
String batchName = downstreamBuild.getJobVariant();
if ((batchName == null) ||
(!batchName.contains("integration") &&
!batchName.contains("unit"))) {
continue;
}
String status = downstreamBuild.getStatus();
if (!status.equals("completed")) {
continue;
}
String result = downstreamBuild.getResult();
if ((result == null) || !result.equals("UNSTABLE")) {
continue;
}
String gitSubrepositoryPackageNames =
JenkinsResultsParserUtil.getProperty(
localLiferayJenkinsEEBuildProperties,
"subrepository.package.names");
if (gitSubrepositoryPackageNames == null) {
continue;
}
for (String gitSubrepositoryPackageName :
gitSubrepositoryPackageNames.split(",")) {
if (!jenkinsJobFailureURLs.isEmpty()) {
break;
}
List testResults = new ArrayList<>();
testResults.addAll(downstreamBuild.getTestResults("FAILED"));
testResults.addAll(
downstreamBuild.getTestResults("REGRESSION"));
for (TestResult testResult : testResults) {
if (!testResult.isUniqueFailure()) {
continue;
}
if (gitSubrepositoryPackageName.equals(
testResult.getPackageName())) {
failedDownstreamBuild = downstreamBuild;
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append(testResult.getClassName());
sb.append("");
jenkinsJobFailureURLs.add(sb.toString());
}
}
}
}
if (!jenkinsJobFailureURLs.isEmpty()) {
pullRequest.close();
StringBuilder sb = new StringBuilder();
sb.append("The pull request tester is still running.
");
sb.append("Please wait until you get the final report");
sb.append(" before running 'ci:retest'.
See this ");
sb.append("link to check on the status of your test:
");
sb.append("@");
sb.append(gitHubSenderUsername);
sb.append("
");
sb.append("However, the pull request was closed.
");
sb.append("The pull request was closed due to the following ");
sb.append("integration/unit test failures:
");
for (String jenkinsJobFailureURL : jenkinsJobFailureURLs) {
sb.append("- ");
sb.append(jenkinsJobFailureURL);
sb.append("
");
}
sb.append("
These test failures are a part of a ");
sb.append("'module group'/'subrepository' that was changed in ");
sb.append("this pull request.
");
sb.append("*This pull will ");
sb.append("no longer automatically close if this comment is ");
sb.append("available. If you believe this is a mistake please ");
sb.append("reopen this pull by entering the following command ");
sb.append("as a comment.
ci:reopen");
sb.append("
Critical Failure Details:
");
try {
sb.append(
Dom4JUtil.format(
failedDownstreamBuild.getGitHubMessageElement(),
false));
}
catch (Exception exception) {
exception.printStackTrace();
throw exception;
}
if (!_autoCloseGitHubCommentMentionUsernames.isEmpty()) {
sb.append("cc");
for (String autoCloseGithubCommentMentionUsername :
_autoCloseGitHubCommentMentionUsernames) {
sb.append(" @");
sb.append(autoCloseGithubCommentMentionUsername);
}
sb.append("");
}
pullRequest.addComment(sb.toString());
return true;
}
return false;
}
public static List getAutoCloseRules(PullRequest pullRequest)
throws Exception {
List list = new ArrayList<>();
String propertyNameTemplate = JenkinsResultsParserUtil.combine(
"test.batch.names.auto.close[",
pullRequest.getGitHubRemoteGitRepositoryName(), "?]");
String gitRepositoryBranchAutoClosePropertyName =
propertyNameTemplate.replace(
"?", "-" + pullRequest.getUpstreamRemoteGitBranchName());
Properties localLiferayJenkinsEEBuildProperties =
JenkinsResultsParserUtil.getLocalLiferayJenkinsEEBuildProperties();
String testBatchNamesAutoClose = JenkinsResultsParserUtil.getProperty(
localLiferayJenkinsEEBuildProperties,
gitRepositoryBranchAutoClosePropertyName);
if (testBatchNamesAutoClose == null) {
String gitRepositoryAutoClosePropertyName =
propertyNameTemplate.replace("?", "");
testBatchNamesAutoClose = JenkinsResultsParserUtil.getProperty(
localLiferayJenkinsEEBuildProperties,
gitRepositoryAutoClosePropertyName);
}
if (testBatchNamesAutoClose != null) {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Finding auto-close rules for ",
gitRepositoryBranchAutoClosePropertyName, "."));
}
String[] autoCloseRuleDataArray = StringUtils.split(
testBatchNamesAutoClose, ",");
for (String autoCloseRuleData : autoCloseRuleDataArray) {
if (autoCloseRuleData.startsWith("#") ||
autoCloseRuleData.startsWith("static_")) {
continue;
}
AutoCloseRule newAutoCloseRule = new AutoCloseRule(
autoCloseRuleData);
if (debug) {
System.out.println("\t" + newAutoCloseRule.toString());
}
list.add(newAutoCloseRule);
}
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Finished finding ",
gitRepositoryBranchAutoClosePropertyName,
" auto-close rules.\n"));
}
}
return list;
}
public static boolean isAutoCloseBranch(PullRequest pullRequest) {
String gitHubRemoteGitRepositoryName =
pullRequest.getGitHubRemoteGitRepositoryName();
String testBranchNamesAutoClose = JenkinsResultsParserUtil.getProperty(
JenkinsResultsParserUtil.getLocalLiferayJenkinsEEBuildProperties(),
JenkinsResultsParserUtil.combine(
"test.branch.names.auto.close[", gitHubRemoteGitRepositoryName,
"]"));
String branchName = pullRequest.getUpstreamRemoteGitBranchName();
if (testBranchNamesAutoClose == null) {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Auto-close rules are deactivated for ",
gitHubRemoteGitRepositoryName, "(", branchName, ")."));
}
return false;
}
List testBranchNamesAutoCloseList = Arrays.asList(
testBranchNamesAutoClose.split(","));
return testBranchNamesAutoCloseList.contains(branchName);
}
public static boolean isAutoCloseOnCriticalTestFailuresActive(
PullRequest pullRequest) {
String criticalTestBranchesString =
JenkinsResultsParserUtil.getProperty(
JenkinsResultsParserUtil.
getLocalLiferayJenkinsEEBuildProperties(),
JenkinsResultsParserUtil.combine(
"test.branch.names.critical.test[",
pullRequest.getGitHubRemoteGitRepositoryName(), "]"));
if ((criticalTestBranchesString == null) ||
criticalTestBranchesString.isEmpty()) {
return false;
}
String[] criticalTestBranches = StringUtils.split(
criticalTestBranchesString, ",");
for (String criticalTestBranch : criticalTestBranches) {
if (criticalTestBranch.equals(
pullRequest.getUpstreamRemoteGitBranchName())) {
return true;
}
}
return false;
}
private static List _getBuildPropertyAsList(String propertyName) {
try {
return JenkinsResultsParserUtil.getBuildPropertyAsList(
true, propertyName);
}
catch (IOException ioException) {
throw new RuntimeException(
"Unable to get property " + propertyName, ioException);
}
}
private static final List _autoCloseGitHubCommentMentionUsernames =
_getBuildPropertyAsList("auto.close.github.comment.mention.usernames");
private static final List _autoCloseReceiverUsernames =
_getBuildPropertyAsList("auto.close.receiver.usernames");
private static class AutoCloseRule {
public AutoCloseRule(String ruleData) {
this.ruleData = ruleData;
String[] ruleDataArray = ruleData.split("\\|");
rulePattern = Pattern.compile(ruleDataArray[0]);
if (ruleDataArray[1].endsWith("%")) {
String percentageRule = ruleDataArray[1];
int i = Integer.parseInt(
percentageRule.substring(0, percentageRule.length() - 1));
maxFailPercentage = i / 100;
}
else {
maxFailCount = Integer.parseInt(ruleDataArray[1]);
}
}
public List evaluate(List downstreamBuilds) {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Evaluating auto-close rule ", toString(), "."));
}
try {
downstreamBuilds = getMatchingBuilds(downstreamBuilds);
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Found ", String.valueOf(downstreamBuilds.size()),
" builds that match this rule."));
}
List failingInUpstreamJobDownstreamBuilds =
new ArrayList<>(downstreamBuilds.size());
for (Build downstreamBuild : downstreamBuilds) {
if (downstreamBuild.isFailing()) {
failingInUpstreamJobDownstreamBuilds.add(
downstreamBuild);
continue;
}
List uniqueFailureTestResults =
downstreamBuild.getUniqueFailureTestResults();
if (uniqueFailureTestResults.isEmpty()) {
failingInUpstreamJobDownstreamBuilds.add(
downstreamBuild);
}
}
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
String.valueOf(
failingInUpstreamJobDownstreamBuilds.size()),
" downstream builds are also failing in ",
"upstream."));
}
downstreamBuilds.removeAll(
failingInUpstreamJobDownstreamBuilds);
if (downstreamBuilds.isEmpty()) {
if (debug) {
System.out.println(toString() + " has PASSED.");
}
return Collections.emptyList();
}
List failedDownstreamBuilds = new ArrayList<>(
downstreamBuilds.size());
int failLimit = 0;
if (maxFailPercentage != -1) {
failLimit =
(int)(maxFailPercentage * downstreamBuilds.size());
if (failLimit > 0) {
failLimit--;
}
}
else {
failLimit = maxFailCount;
}
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
toString(), " fail limit is ",
String.valueOf(failLimit)));
}
for (Build downstreamBuild : downstreamBuilds) {
String status = downstreamBuild.getStatus();
if (!status.equals("completed")) {
continue;
}
String result = downstreamBuild.getResult();
if ((result != null) && !result.equals("SUCCESS")) {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Found a matching failed build. ",
downstreamBuild.getDisplayName(),
" has failed."));
}
failedDownstreamBuilds.add(downstreamBuild);
}
}
if (failedDownstreamBuilds.size() > failLimit) {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Found ",
String.valueOf(failedDownstreamBuilds.size()),
" matching failed builds.\n", toString(),
" has FAILED."));
}
return failedDownstreamBuilds;
}
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Found ",
String.valueOf(failedDownstreamBuilds.size()),
" matching failed builds.\n", toString(),
" has PASSED."));
}
return Collections.emptyList();
}
finally {
if (debug) {
System.out.println(
JenkinsResultsParserUtil.combine(
"Finished evaluating rule ", toString(), "\n"));
}
}
}
@Override
public String toString() {
return ruleData;
}
protected List getMatchingBuilds(List downstreamBuilds) {
List filteredDownstreamBuilds = new ArrayList<>(
downstreamBuilds.size());
for (Build downstreamBuild : downstreamBuilds) {
String jobVariant = downstreamBuild.getJobVariant();
if ((jobVariant != null) && !jobVariant.isEmpty()) {
Matcher matcher = rulePattern.matcher(jobVariant);
if (matcher.matches()) {
filteredDownstreamBuilds.add(downstreamBuild);
continue;
}
}
String jobName = downstreamBuild.getJobName();
if ((jobName != null) && !jobName.isEmpty()) {
Matcher matcher = rulePattern.matcher(jobName);
if (matcher.matches()) {
filteredDownstreamBuilds.add(downstreamBuild);
}
}
}
return filteredDownstreamBuilds;
}
protected int maxFailCount = -1;
protected float maxFailPercentage = -1;
protected String ruleData;
protected Pattern rulePattern;
}
}