All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.jenkins.results.parser.AutoCloseUtil Maven / Gradle / Ivy

The newest version!
/**
 * 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:

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; } }