/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.jenkins.results.parser; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dom4j.Element; import org.json.JSONObject; /** * @author Kevin Yen */ public class TopLevelBuild extends BaseBuild { @Override public void archive(String archiveName) { super.archive(archiveName); if (getParentBuild() == null) { Properties archiveProperties = new Properties(); archiveProperties.setProperty( "top.level.build.url", replaceBuildURL(getBuildURL())); try { StringWriter sw = new StringWriter(); archiveProperties.store(sw, null); writeArchiveFile( sw.toString(), archiveName + "/archive.properties"); } catch (IOException ioe) { throw new RuntimeException( "Unable to write archive properties"); } } try { writeArchiveFile( getJenkinsReport(), getArchivePath() + "/jenkins-report.html"); } catch (IOException ioe) { throw new RuntimeException("Unable to archive Jenkins report", ioe); } } public Map<String, String> getBaseGitRepositoryDetailsTempMap() { String repositoryType = getBaseRepositoryType(); String tempMapName = "git." + repositoryType + ".properties"; return getTempMap(tempMapName); } @Override public String getDisplayName() { String displayName = super.getDisplayName(); if (getParentBuild() != null) { displayName += "/" + getParameterValue("JENKINS_JOB_VARIANT"); } return displayName; } @Override public Element getGitHubMessageElement() { Collections.sort( downstreamBuilds, new BaseBuild.BuildDisplayNameComparator()); if (getParentBuild() == null) { return getTopGitHubMessageElement(); } return super.getGitHubMessageElement(); } public String getJenkinsReport() { try { return JenkinsResultsParserUtil.toString( JenkinsResultsParserUtil.getLocalURL(getJenkinsReportURL())); } catch (IOException ioe) { throw new RuntimeException("Unable to get Jenkins report", ioe); } } public String getJenkinsReportURL() { if (fromArchive) { return getBuildURL() + "/jenkins-report.html"; } return JenkinsResultsParserUtil.combine( "https://", getMaster(), ".liferay.com/", "userContent/jobs/", getJobName(), "/builds/", Integer.toString(getBuildNumber()), "/jenkins-report.html"); } @Override public String getStatusReport(int indentSize) { String statusReport = super.getStatusReport(indentSize); if (getDownstreamBuildCount(null) > 0) { while (statusReport.endsWith("\n")) { statusReport = statusReport.substring( 0, statusReport.length() - 1); } statusReport += " / "; } return statusReport + "Update took " + _updateDuration + " milliseconds.\n"; } @Override public String getStatusSummary() { long currentTimeMillis = System.currentTimeMillis(); if ((currentTimeMillis - _DOWNSTREAM_BUILDS_LISTING_INTERVAL) >= _lastDownstreamBuildsListingTimestamp) { StringBuilder sb = new StringBuilder(super.getStatusSummary()); sb.append("\nRunning Builds: "); _lastDownstreamBuildsListingTimestamp = System.currentTimeMillis(); for (Build downstreamBuild : getDownstreamBuilds("running")) { sb.append("\n"); sb.append(downstreamBuild.getBuildURL()); } return sb.toString(); } return super.getStatusSummary(); } @Override public JSONObject getTestReportJSONObject() { return null; } @Override public void update() { long start = System.currentTimeMillis(); super.update(); _updateDuration = System.currentTimeMillis() - start; } protected TopLevelBuild(String url) { this(url, null); } protected TopLevelBuild(String url, TopLevelBuild topLevelBuild) { super(url, topLevelBuild); } @Override protected void archiveJSON() { super.archiveJSON(); try { Properties buildProperties = JenkinsResultsParserUtil.getBuildProperties(); String repositoryTypes = buildProperties.getProperty( "repository.types"); if (jobName.startsWith( "test-subrepository-acceptance-pullrequest")) { repositoryTypes += "," + getBaseRepositoryName(); } for (String repositoryType : repositoryTypes.split(",")) { try { JSONObject gitRepositoryDetailsJSONObject = JenkinsResultsParserUtil.toJSONObject( getGitRepositoryDetailsPropertiesTempMapURL( repositoryType)); Set<?> set = gitRepositoryDetailsJSONObject.keySet(); if (set.isEmpty()) { continue; } writeArchiveFile( gitRepositoryDetailsJSONObject.toString(4), getArchivePath() + "/git." + repositoryType + ".properties.json"); } catch (IOException ioe) { throw new RuntimeException( "Unable to create git." + repositoryType + ".properties.json", ioe); } } } catch (IOException ioe) { throw new RuntimeException("Unable to get build properties", ioe); } } @Override protected void findDownstreamBuilds() { if (getParentBuild() != null) { return; } super.findDownstreamBuilds(); } @Override protected List<String> findDownstreamBuildsInConsoleText( String consoleText) { if (getParentBuild() != null) { return Collections.emptyList(); } return super.findDownstreamBuildsInConsoleText(consoleText); } protected Element getBaseBranchDetailsElement() { String baseBranchURL = "https://github.com/liferay/" + getBaseRepositoryName() + "/tree/" + getBranchName(); String baseRepositoryName = getBaseRepositoryName(); String baseRepositorySHA = null; if (!baseRepositoryName.equals("liferay-jenkins-ee") && baseRepositoryName.endsWith("-ee")) { baseRepositorySHA = getBaseRepositorySHA( baseRepositoryName.substring( 0, baseRepositoryName.length() - 3)); } else { baseRepositorySHA = getBaseRepositorySHA(baseRepositoryName); } String baseRepositoryCommitURL = "https://github.com/liferay/" + baseRepositoryName + "/commit/" + baseRepositorySHA; Element baseBranchDetailsElement = Dom4JUtil.getNewElement( "p", null, "Branch Name: ", Dom4JUtil.getNewAnchorElement(baseBranchURL, getBranchName())); if (baseRepositorySHA != null) { Dom4JUtil.addToElement( baseBranchDetailsElement, Dom4JUtil.getNewElement("br"), "Branch GIT ID: ", Dom4JUtil.getNewAnchorElement( baseRepositoryCommitURL, baseRepositorySHA)); } return baseBranchDetailsElement; } protected Element getBuildTimeElement() { return Dom4JUtil.getNewElement( "p", null, "Build Time: ", JenkinsResultsParserUtil.toDurationString(getDuration())); } protected Element getDownstreamGitHubMessageElement() { String status = getStatus(); if (!status.equals("completed") && (getParentBuild() != null)) { return null; } String result = getResult(); if (result.equals("SUCCESS")) { return null; } Element messageElement = Dom4JUtil.getNewElement( "div", null, Dom4JUtil.getNewAnchorElement( getBuildURL(), null, getDisplayName())); if (result.equals("ABORTED")) { messageElement.add( Dom4JUtil.toCodeSnippetElement("Build was aborted")); } if (result.equals("FAILURE")) { Element failureMessageElement = getFailureMessageElement(); if (failureMessageElement != null) { messageElement.add(failureMessageElement); } } return messageElement; } @Override protected ExecutorService getExecutorService() { return Executors.newFixedThreadPool(20); } @Override protected FailureMessageGenerator[] getFailureMessageGenerators() { return _FAILURE_MESSAGE_GENERATORS; } @Override protected Element getGitHubMessageJobResultsElement() { int successCount = getDownstreamBuildCountByResult("SUCCESS"); int failCount = getDownstreamBuildCount(null) - successCount + 1; return Dom4JUtil.getNewElement( "div", null, Dom4JUtil.getNewElement("h6", null, "Job Results:"), Dom4JUtil.getNewElement( "p", null, Integer.toString(successCount), JenkinsResultsParserUtil.getNounForm( successCount, " Jobs", " Job"), " Passed.", Dom4JUtil.getNewElement("br"), Integer.toString(failCount), JenkinsResultsParserUtil.getNounForm( failCount, " Jobs", " Job"), " Failed.")); } protected String getGitRepositoryDetailsPropertiesTempMapURL( String repositoryType) { if (fromArchive) { return JenkinsResultsParserUtil.combine( getBuildURL(), "git.", repositoryType, ".properties.json"); } TopLevelBuild topLevelBuild = getTopLevelBuild(); return JenkinsResultsParserUtil.combine( tempMapBaseURL, topLevelBuild.getMaster(), "/", topLevelBuild.getJobName(), "/", Integer.toString(topLevelBuild.getBuildNumber()), "/", topLevelBuild.getJobName(), "/git.", repositoryType, ".properties"); } protected Element getJobSummaryListElement() { Element jobSummaryListElement = Dom4JUtil.getNewElement("ul"); List<Build> builds = new ArrayList<>(); builds.add(this); builds.addAll(getDownstreamBuilds(null)); for (Build build : builds) { Element jobSummaryListItemElement = Dom4JUtil.getNewElement( "li", jobSummaryListElement); jobSummaryListItemElement.add( build.getGitHubMessageBuildAnchorElement()); } return jobSummaryListElement; } protected Element getMoreDetailsElement() { Element moreDetailsElement = Dom4JUtil.getNewElement( "h5", null, "For more details click ", Dom4JUtil.getNewAnchorElement(getJenkinsReportURL(), "here"), "."); return moreDetailsElement; } protected Element getResultElement() { Element resultElement = Dom4JUtil.getNewElement("h1"); String result = getResult(); if (!result.equals("SUCCESS")) { resultElement.addText("Some tests FAILED."); } else { resultElement.addText("All tests PASSED."); } return resultElement; } @Override protected String getStartPropertiesTempMapURL() { if (fromArchive) { return getBuildURL() + "/start.properties.json"; } return JenkinsResultsParserUtil.combine( tempMapBaseURL, getMaster(), "/", getJobName(), "/", Integer.toString(getBuildNumber()), "/", getJobName(), "/", "start.properties"); } @Override protected String getStopPropertiesTempMapURL() { if (fromArchive) { return getBuildURL() + "/stop.properties.json"; } return JenkinsResultsParserUtil.combine( tempMapBaseURL, getMaster(), "/", getJobName(), "/", Integer.toString(getBuildNumber()), "/", getJobName(), "/", "stop.properties"); } @Override protected String getTempMapURL(String tempMapName) { String tempMapURL = super.getTempMapURL(tempMapName); if (tempMapURL != null) { return tempMapURL; } Matcher matcher = gitRepositoryTempMapNamePattern.matcher(tempMapName); if (matcher.find()) { return getGitRepositoryDetailsPropertiesTempMapURL( matcher.group("repositoryType")); } return null; } protected Element getTopGitHubMessageElement() { update(); Element rootElement = Dom4JUtil.getNewElement( "html", null, getResultElement(), getBuildTimeElement(), Dom4JUtil.getNewElement("h4", null, "Base Branch:"), getBaseBranchDetailsElement(), Dom4JUtil.getNewElement("h4", null, "Job Summary:"), getJobSummaryListElement(), getMoreDetailsElement()); String result = getResult(); if (!result.equals("SUCCESS")) { Dom4JUtil.addToElement( rootElement, Dom4JUtil.getNewElement("hr"), Dom4JUtil.getNewElement("h4", null, "Failed Jobs:")); Element failedJobsOrderedListElement = Dom4JUtil.getNewElement( "ol", rootElement, Dom4JUtil.getNewElement( "li", null, super.getGitHubMessageElement())); int failureCount = 1; List<Element> failureElements = new ArrayList<>(); for (Build downstreamBuild : getDownstreamBuilds(null)) { String downstreamBuildResult = downstreamBuild.getResult(); if (downstreamBuildResult.equals("SUCCESS")) { continue; } Element failureElement = downstreamBuild.getGitHubMessageElement(); if (isHighPriorityBuildFailureElement(failureElement)) { failureElements.add(0, failureElement); continue; } failureElements.add(downstreamBuild.getGitHubMessageElement()); } for (Element failureElement : failureElements) { Element failedJobsListItemElement = Dom4JUtil.getNewElement( "li", failedJobsOrderedListElement); if (failureCount == 5) { failedJobsListItemElement.addText("..."); break; } failedJobsListItemElement.add(failureElement); failureCount++; } String jobName = getJobName(); if (jobName.contains("pullrequest")) { String url = JenkinsResultsParserUtil.combine( "https://test-1-1.liferay.com/job/", jobName.replace("pullrequest", "upstream")); try { JenkinsResultsParserUtil.toString( JenkinsResultsParserUtil.getLocalURL(url), false, 0, 0, 0); Dom4JUtil.addToElement( Dom4JUtil.getNewElement("h5", rootElement), "For upstream results, click ", Dom4JUtil.getNewAnchorElement(url, "here"), "."); } catch (IOException ioe) { System.out.println("No upstream build detected."); } } } return rootElement; } protected String getUpstreamBranchSHA() { String upstreamBranchSHA = getParameterValue( "GITHUB_UPSTREAM_BRANCH_SHA"); if ((upstreamBranchSHA == null) || upstreamBranchSHA.isEmpty()) { Map<String, String> startPropertiesTempMap = getStartPropertiesTempMap(); upstreamBranchSHA = startPropertiesTempMap.get( "GITHUB_UPSTREAM_BRANCH_SHA"); } return upstreamBranchSHA; } protected static final Pattern gitRepositoryTempMapNamePattern = Pattern.compile("git\\.(?<repositoryType>.*)\\.properties"); private static final long _DOWNSTREAM_BUILDS_LISTING_INTERVAL = 1000 * 60 * 5; private static final FailureMessageGenerator[] _FAILURE_MESSAGE_GENERATORS = { new PoshiValidationFailureMessageGenerator(), new RebaseFailureMessageGenerator(), new DownstreamFailureMessageGenerator(), new GenericFailureMessageGenerator() }; private long _lastDownstreamBuildsListingTimestamp = -1L; private long _updateDuration; }