/** * List of users who committed a change since the last non-broken build till now. * * <p>This list at least always include people who made changes in this build, but if the previous * build was a failure it also includes the culprit list from there. Culprits of unstable build * are also included see <a href="http://issues.hudson-ci.org/browse/HUDSON-4617">HUDSON-4617</a> * for details * * @return can be empty but never null. */ @Exported public Set<User> getCulprits() { if (culprits == null) { Set<User> r = new HashSet<User>(); R p = getPreviousCompletedBuild(); if (p != null && isBuilding()) { Result pr = p.getResult(); if (pr != null && pr.isWorseOrEqualTo(Result.UNSTABLE)) { // we are still building, so this is just the current latest information, // but we seems to be failing so far, so inherit culprits from the previous build. // isBuilding() check is to avoid recursion when loading data from old Hudson, which // doesn't record // this information r.addAll(p.getCulprits()); } } for (Entry e : getChangeSet()) r.add(e.getAuthor()); if (upstreamCulprits) { // If we have dependencies since the last successful build, add their authors to our list R previousBuild = getPreviousSuccessfulBuild(); if (previousBuild != null) { Map<AbstractProject, AbstractBuild.DependencyChange> depmap = getDependencyChanges(previousBuild); for (AbstractBuild.DependencyChange dep : depmap.values()) { for (AbstractBuild<?, ?> b : dep.getBuilds()) { for (Entry entry : b.getChangeSet()) { r.add(entry.getAuthor()); } } } } } return r; } return new AbstractSet<User>() { public Iterator<User> iterator() { return new AdaptedIterator<String, User>(culprits.iterator()) { protected User adapt(String id) { return User.get(id); } }; } public int size() { return culprits.size(); } }; }
@DataBoundConstructor public SelfHealingMatrixExecutionStrategy( String logPattern, Result worseThanOrEqualTo, Result betterThanOrEqualTo, int maxRetries, boolean stopRetryingAfterOneFails) { this.logPattern = logPattern == null ? "" : logPattern; this.worseThanOrEqualTo = worseThanOrEqualTo == null ? Result.FAILURE : worseThanOrEqualTo; this.betterThanOrEqualTo = betterThanOrEqualTo == null ? Result.ABORTED : betterThanOrEqualTo.isWorseOrEqualTo(this.worseThanOrEqualTo) ? betterThanOrEqualTo : this.worseThanOrEqualTo; this.maxRetries = maxRetries < 0 ? 1 : maxRetries; this.stopRetryingAfterOneFails = stopRetryingAfterOneFails; }
/** * Waits for the given configurations to finish, retrying any that qualify to be rerun. * * @param execution Provided by the plugin. * @param patterns List of regular expression patterns used to scan the log to determine if a * build should be rerun. * @param retries Mutable map that tracks the number of times a specific configuration has been * retried. * @param configurations The configurations that have already been scheduled to run that should be * waited for to finish. * @return The worst result of all the runs. If a build was rerun, only the result of the rerun is * considered. * @throws InterruptedException * @throws IOException */ private Result waitForMatrixRuns( MatrixBuild.MatrixBuildExecution execution, List<Pattern> patterns, Map<MatrixConfiguration, Integer> retries, LinkedList<MatrixConfiguration> configurations) throws InterruptedException, IOException { BuildListener listener = execution.getListener(); PrintStream logger = listener.getLogger(); Map<String, String> whyBlockedMap = new HashMap< String, String>(); // keep track of why builds are blocked so we can print unique messages when // they change. Result finalResult = Result.SUCCESS; int iteration = 0; boolean continueRetrying = true; while (!configurations.isEmpty()) { ++iteration; MatrixConfiguration configuration = configurations.removeFirst(); if (isBuilding(execution, configuration, whyBlockedMap)) { if (iteration >= configurations.size()) { // Every time we loop through all the configurations, sleep for a bit. // This is to prevent polling too often while everything is still building. iteration = 0; Thread.sleep(1000); } configurations.add(configuration); continue; } Run parentBuild = execution.getBuild(); MatrixRun matrixRun = configuration.getBuildByNumber(parentBuild.getNumber()); Result runResult = matrixRun.getResult(); if (continueRetrying && runResult.isWorseOrEqualTo(getWorseThanOrEqualTo()) && runResult.isBetterOrEqualTo(getBetterThanOrEqualTo())) { if (matchesPattern(matrixRun, patterns)) { int retriedCount = retries.get(configuration); if (retriedCount < getMaxRetries()) { ++retriedCount; retries.put(configuration, retriedCount); // rerun String logMessage = String.format( "%s was %s. Matched pattern to rerun. Rerunning (%d).", matrixRun, runResult, retriedCount); listener.error(logMessage); HealedAction action = parentBuild.getAction(HealedAction.class); if (action == null) { //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (parentBuild.getActions()) { action = parentBuild.getAction(HealedAction.class); if (action == null) { action = new HealedAction(matrixRun.getCharset()); parentBuild.addAction(action); } } } action.addAutoHealedJob(matrixRun); MatrixConfiguration parent = matrixRun.getParent(); if (parent != null) { // I'm paranoid about NPEs parent.removeRun(matrixRun); matrixRun.delete(); } else { LOGGER.severe( "couldn't remove old run, parent was null. This is a Jenkins core bug."); } scheduleConfigurationBuild( execution, configuration, new SelfHealingCause(parentBuild, retriedCount)); configurations.add(configuration); continue; } else { String logMessage = String.format( "%s was %s. Matched pattern to rerun, but the max number of retries (%d) has been met.", matrixRun, runResult, getMaxRetries()); listener.error(logMessage); if (getStopRetryingAfterOneFails()) { listener.error("Not retrying any more builds."); continueRetrying = false; } } } else { String logMessage = String.format( "%s was %s. It did not match the pattern to rerun. Accepting result.", matrixRun, runResult); logger.println(logMessage); } } notifyEndRun(matrixRun, execution.getAggregators(), execution.getListener()); finalResult = finalResult.combine(runResult); } return finalResult; }