/**
* 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.hadoop.hbase.devtools.buildstats;
import com.offbytwo.jenkins.JenkinsServer;
import com.offbytwo.jenkins.client.JenkinsHttpClient;
import com.offbytwo.jenkins.model.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
public class TestResultHistory {
public final static String STATUS_REGRESSION = "REGRESSION";
public final static String STATUS_FAILED = "FAILED";
public final static String STATUS_PASSED = "PASSED";
public final static String STATUS_FIXED = "FIXED";
public static int BUILD_HISTORY_NUM = 15;
private JenkinsHttpClient client;
private String jobName;
public TestResultHistory(String apacheHTTPURL, String jobName, String userName, String passWord)
throws URISyntaxException {
this.client = new JenkinsHttpClient(new URI(apacheHTTPURL), userName, passWord);
this.jobName = jobName;
}
public static void main(String[] args) {
if (args.length < 2) {
printUsage();
return;
}
String apacheHTTPUrl = args[0];
String jobName = args[1];
if (args.length > 2) {
int tmpHistoryJobNum = -1;
try {
tmpHistoryJobNum = Integer.parseInt(args[2]);
} catch (NumberFormatException ex) {
// ignore
}
if (tmpHistoryJobNum > 0) {
BUILD_HISTORY_NUM = tmpHistoryJobNum;
}
}
try {
TestResultHistory buildHistory = new TestResultHistory(apacheHTTPUrl, jobName, "", "");
HistoryReport report = buildHistory.getReport();
// display result in console
report.printReport();
} catch (Exception ex) {
System.out.println("Got unexpected exception: " + ex.getMessage());
}
}
protected static void printUsage() {
System.out.println("<Jenkins HTTP URL> <Job Name> [Number of Historical Jobs to Check]");
System.out.println("Sample Input: \"https://builds.apache.org\" "
+ "\"HBase-TRUNK-on-Hadoop-2.0.0\" ");
}
public HistoryReport getReport() {
HistoryReport report = new HistoryReport();
List<Integer> buildWithTestResults = new ArrayList<Integer>();
Map<String, int[]> failureStats = new HashMap<String, int[]>();
try {
JenkinsServer jenkins = new JenkinsServer(this.client);
Map<String, Job> jobs = jenkins.getJobs();
JobWithDetails job = jobs.get(jobName.toLowerCase()).details();
// build test case failures stats for the past 10 builds
Build lastBuild = job.getLastBuild();
int startingBuildNumber =
(lastBuild.getNumber() - BUILD_HISTORY_NUM > 0) ? lastBuild.getNumber()
- BUILD_HISTORY_NUM + 1 : 1;
Map<Integer, HashMap<String, String>> executedTestCases =
new HashMap<Integer, HashMap<String, String>>();
Map<Integer, Set<String>> skippedTestCases = new TreeMap<Integer, Set<String>>();
Set<String> allExecutedTestCases = new HashSet<String>();
Map<Integer, Set<String>> normalizedTestSet = new HashMap<Integer, Set<String>>();
String buildUrl = lastBuild.getUrl();
for (int i = startingBuildNumber; i <= lastBuild.getNumber(); i++) {
HashMap<String, String> buildExecutedTestCases = new HashMap<String, String>(2048);
String curBuildUrl = buildUrl.replaceFirst("/" + lastBuild.getNumber(), "/" + i);
List<String> failedCases = null;
try {
failedCases = getBuildFailedTestCases(curBuildUrl, buildExecutedTestCases);
buildWithTestResults.add(i);
} catch (Exception ex) {
// can't get result so skip it
continue;
}
executedTestCases.put(i, buildExecutedTestCases);
HashSet<String> tmpSet = new HashSet<String>();
for (String tmpTestCase : buildExecutedTestCases.keySet()) {
allExecutedTestCases.add(tmpTestCase.substring(0, tmpTestCase.lastIndexOf(".")));
tmpSet.add(tmpTestCase.substring(0, tmpTestCase.lastIndexOf(".")));
}
normalizedTestSet.put(i, tmpSet);
// set test result failed cases of current build
for (String curFailedTestCase : failedCases) {
if (failureStats.containsKey(curFailedTestCase)) {
int[] testCaseResultArray = failureStats.get(curFailedTestCase);
testCaseResultArray[i - startingBuildNumber] = -1;
} else {
int[] testResult = new int[BUILD_HISTORY_NUM];
testResult[i - startingBuildNumber] = -1;
// refill previous build test results for newly failed test case
for (int k = startingBuildNumber; k < i; k++) {
HashMap<String, String> tmpBuildExecutedTestCases = executedTestCases.get(k);
if (tmpBuildExecutedTestCases != null
&& tmpBuildExecutedTestCases.containsKey(curFailedTestCase)) {
String statusStr = tmpBuildExecutedTestCases.get(curFailedTestCase);
testResult[k - startingBuildNumber] = convertStatusStringToInt(statusStr);
}
}
failureStats.put(curFailedTestCase, testResult);
}
}
// set test result for previous failed test cases
for (String curTestCase : failureStats.keySet()) {
if (!failedCases.contains(curTestCase) && buildExecutedTestCases.containsKey(curTestCase)) {
String statusVal = buildExecutedTestCases.get(curTestCase);
int[] testCaseResultArray = failureStats.get(curTestCase);
testCaseResultArray[i - startingBuildNumber] = convertStatusStringToInt(statusVal);
}
}
}
// check which test suits skipped
for (int i = startingBuildNumber; i <= lastBuild.getNumber(); i++) {
Set<String> skippedTests = new HashSet<String>();
HashMap<String, String> tmpBuildExecutedTestCases = executedTestCases.get(i);
if (tmpBuildExecutedTestCases == null || tmpBuildExecutedTestCases.isEmpty()) continue;
// normalize test case names
Set<String> tmpNormalizedTestCaseSet = normalizedTestSet.get(i);
for (String testCase : allExecutedTestCases) {
if (!tmpNormalizedTestCaseSet.contains(testCase)) {
skippedTests.add(testCase);
}
}
skippedTestCases.put(i, skippedTests);
}
report.setBuildsWithTestResults(buildWithTestResults);
for (String failedTestCase : failureStats.keySet()) {
int[] resultHistory = failureStats.get(failedTestCase);
int[] compactHistory = new int[buildWithTestResults.size()];
int index = 0;
for (Integer i : buildWithTestResults) {
compactHistory[index] = resultHistory[i - startingBuildNumber];
index++;
}
failureStats.put(failedTestCase, compactHistory);
}
report.setHistoryResults(failureStats, skippedTestCases);
} catch (Exception ex) {
System.out.println(ex);
ex.printStackTrace();
}
return report;
}
/**
* @param statusVal
* @return 1 means PASSED, -1 means FAILED, 0 means SKIPPED
*/
static int convertStatusStringToInt(String statusVal) {
if (statusVal.equalsIgnoreCase(STATUS_REGRESSION) || statusVal.equalsIgnoreCase(STATUS_FAILED)) {
return -1;
} else if (statusVal.equalsIgnoreCase(STATUS_PASSED)) {
return 1;
}
return 0;
}
/**
* Get failed test cases of a build
* @param buildURL Jenkins build job URL
* @param executedTestCases Set of test cases which was executed for the build
* @return list of failed test case names
*/
List<String> getBuildFailedTestCases(String buildURL, HashMap<String, String> executedTestCases)
throws IOException {
List<String> result = new ArrayList<String>();
String apiPath =
urlJoin(buildURL,
"testReport?depth=10&tree=suites[cases[className,name,status,failedSince]]");
List<TestSuite> suites = client.get(apiPath, BuildResultWithTestCaseDetails.class).getSuites();
result = getTestSuiteFailedTestcase(suites, executedTestCases);
return result;
}
private List<String> getTestSuiteFailedTestcase(List<TestSuite> suites,
HashMap<String, String> executedTestCases) {
List<String> result = new ArrayList<String>();
if (suites == null) {
return result;
}
for (TestSuite curTestSuite : suites) {
for (TestCaseResult curTestCaseResult : curTestSuite.getCases()) {
if (curTestCaseResult.getStatus().equalsIgnoreCase(STATUS_FAILED)
|| curTestCaseResult.getStatus().equalsIgnoreCase(STATUS_REGRESSION)) {
// failed test case
result.add(curTestCaseResult.getFullName());
}
executedTestCases.put(curTestCaseResult.getFullName(), curTestCaseResult.getStatus());
}
}
return result;
}
String urlJoin(String path1, String path2) {
if (!path1.endsWith("/")) {
path1 += "/";
}
if (path2.startsWith("/")) {
path2 = path2.substring(1);
}
return path1 + path2;
}
}