/* Copyright 2014 Google Inc. All rights reserved. * * Licensed 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 com.google.jenkins.flakyTestHandler.plugin.deflake; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.jenkins.flakyTestHandler.plugin.FlakyTestResultAction; import hudson.model.Job; import hudson.model.Run; import net.sf.json.JSONObject; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.servlet.ServletException; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BooleanParameterValue; import hudson.model.CauseAction; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.StringParameterValue; import jenkins.model.Jenkins; /** * Deflake action used to configure and trigger deflake build * * @author Qingzhou Luo */ public class DeflakeAction implements Action { private static final String DEFLAKE_CONFIG_URL = "deflakeConfig"; private static final String RERUN_FAILING_TESTS_COUNT_PARAM = "rerunFailingTestsCountParam"; private static final String MAVEN_TEST_PARAM = "testParam"; private static final String MAVEN_TEST = "test"; private static final String COMMA = ","; private static final String SHARP = "#"; private static final String PLUS = "+"; private static final Function<Map.Entry<String, Set<String>>, String> CLASS_METHOD_MAP_TO_MAVEN_TESTS_LIST = new Function<Entry<String, Set<String>>, String>() { @Override @Nonnull public String apply(@Nonnull Entry<String, Set<String>> entry) { return entry.getKey() + SHARP + Joiner.on(PLUS).join(entry.getValue()); } }; private final Map<String, Set<String>> failingClassMethodMap; public DeflakeAction(Map<String, Set<String>> failingClassMethodMap) { this.failingClassMethodMap = failingClassMethodMap; } @Override public String getIconFileName() { return "clock.gif"; } @Override public String getDisplayName() { return "Deflake this build"; } @Override public String getUrlName() { return "deflake"; } /** * Handles the rebuild request and redirects to deflake config page * * @param request StaplerRequest the request. * @param response StaplerResponse the response handler. * @throws java.io.IOException in case of Stapler issues * @throws javax.servlet.ServletException if something unfortunate happens. * @throws InterruptedException if something unfortunate happens. */ public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException, ServletException, InterruptedException { Run currentBuild = request.findAncestorObject(Run.class); if (currentBuild != null) { Job job = currentBuild.getParent(); job.checkPermission(AbstractProject.BUILD); response.sendRedirect(DEFLAKE_CONFIG_URL); } } /** * Get parameters from submitted form and submit deflake request */ @RequirePOST public void doSubmitDeflakeRequest(StaplerRequest request, StaplerResponse response) throws IOException, ServletException, InterruptedException { Run run = request.findAncestorObject(Run.class); if (run != null) { Job job = run.getParent(); job.checkPermission(AbstractProject.BUILD); List<Action> actions = constructDeflakeCause(run); JSONObject formData = request.getSubmittedForm(); List<ParameterValue> parameterValues = new ArrayList<ParameterValue>(); parameterValues.add(getStringParam(formData, RERUN_FAILING_TESTS_COUNT_PARAM)); JSONObject paramObj = JSONObject.fromObject(formData.get(MAVEN_TEST_PARAM)); boolean onlyRunFailingTests = paramObj.getBoolean("value"); if (onlyRunFailingTests) { String testParameter = generateMavenTestParams(); if (testParameter != null) { parameterValues.add(new StringParameterValue(MAVEN_TEST, testParameter)); } } ParametersAction originalParamAction = run.getAction(ParametersAction.class); if (originalParamAction == null) { originalParamAction = new ParametersAction(); } actions.add(originalParamAction.createUpdated(parameterValues)); Jenkins.getInstance().getQueue().schedule((Queue.Task) run.getParent(), 0, actions); } response.sendRedirect("../../"); } /** * Generate maven test parameters to run all the failed tests * * @return a string in the format of testClass1#testMethod1+testMethod2,testClass2#testMethod3, * ... */ String generateMavenTestParams() { return Joiner.on(COMMA).join(Iterables.transform(failingClassMethodMap.entrySet(), CLASS_METHOD_MAP_TO_MAVEN_TESTS_LIST)); } /** * Construct a list of actions which contain deflake cause and the original failed build * * @param up upstream build * @return list with all original causes and a {@link hudson.model.Cause.UserIdCause} and a {@link * com.google.jenkins.flakyTestHandler.plugin.deflake.DeflakeCause}. */ private static List<Action> constructDeflakeCause(Run up) { List<Action> actions = new ArrayList<Action>(); actions.add(new CauseAction(new DeflakeCause(up))); return actions; } private static ParameterValue getBooleanParam(JSONObject formData, String paramName) { JSONObject paramObj = JSONObject.fromObject(formData.get(paramName)); String name = paramObj.getString("name"); FlakyTestResultAction.logger.log(Level.FINE, "Param: " + name + " with value: " + paramObj.getBoolean("value")); return new BooleanParameterValue(name, paramObj.getBoolean("value")); } private static ParameterValue getStringParam(JSONObject formData, String paramName) { JSONObject paramObj = JSONObject.fromObject(formData.get(paramName)); String name = paramObj.getString("name"); FlakyTestResultAction.logger.log(Level.FINE, "Param: " + name + " with value: " + paramObj.getString("value")); return new StringParameterValue(name, paramObj.getString("value")); } }