From 5a07dfe923a18c00f7295da75274548ff65fb984 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 17 May 2017 09:59:53 +0200 Subject: [PATCH] Aggregate buildStarted messages based on interval provided by user --- .../gerrit/trigger/config/PluginConfig.java | 33 ++ .../gerritnotifier/GerritNotifier.java | 18 +- .../gerritnotifier/NotificationFactory.java | 10 +- .../gerritnotifier/ParameterExpander.java | 132 +++++++- .../gerritnotifier/ToGerritRunListener.java | 94 +++++- .../job/rest/BuildStartedRestCommandJob.java | 21 +- .../job/ssh/BuildStartedCommandJob.java | 16 +- .../gerritnotifier/model/BuildMemory.java | 43 ++- .../model/BuildsStartedStats.java | 52 ++- .../trigger/GerritManagement/index.jelly | 6 + .../help-AggregateJobStartedInterval.html | 7 + .../gerritnotifier/ParameterExpanderTest.java | 116 +++++-- .../ToGerritRunListenerTest.java | 303 ++++++++++++++++-- .../mock/MockGerritHudsonTriggerConfig.java | 39 ++- 14 files changed, 737 insertions(+), 153 deletions(-) create mode 100644 src/main/webapp/help-AggregateJobStartedInterval.html diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/config/PluginConfig.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/config/PluginConfig.java index aa2fcd92b..8484fc4ad 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/config/PluginConfig.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/config/PluginConfig.java @@ -45,10 +45,15 @@ public class PluginConfig implements GerritWorkersConfig { * Default number of sending worker threads. */ public static final int DEFAULT_NR_OF_SENDING_WORKER_THREADS = 1; + /** + * Default time for sending feedback aggregation. + */ + public static final int DEFAULT_AGGREGATION_TIME = 0; private int numberOfReceivingWorkerThreads; private int numberOfSendingWorkerThreads; private int replicationCacheExpirationInMinutes; + private int aggregateJobStartedInterval; /** * Constructs a config with default data. @@ -75,6 +80,7 @@ public PluginConfig(PluginConfig pluginConfig) { numberOfReceivingWorkerThreads = pluginConfig.getNumberOfReceivingWorkerThreads(); numberOfSendingWorkerThreads = pluginConfig.getNumberOfSendingWorkerThreads(); replicationCacheExpirationInMinutes = pluginConfig.getReplicationCacheExpirationInMinutes(); + aggregateJobStartedInterval = pluginConfig.getAggregateJobStartedInterval(); } /** @@ -112,6 +118,11 @@ public void setValues(JSONObject formData) { if (replicationCacheExpirationInMinutes <= 0) { replicationCacheExpirationInMinutes = ReplicationCache.DEFAULT_EXPIRATION_IN_MINUTES; } + + aggregateJobStartedInterval = formData.optInt("aggregateJobStartedInterval", DEFAULT_AGGREGATION_TIME); + if (aggregateJobStartedInterval <= 0) { + aggregateJobStartedInterval = DEFAULT_AGGREGATION_TIME; + } } /** @@ -178,4 +189,26 @@ public int getReplicationCacheExpirationInMinutes() { public void setReplicationCacheExpirationInMinutes(int replicationCacheExpirationInMinutes) { this.replicationCacheExpirationInMinutes = replicationCacheExpirationInMinutes; } + + /** + * Aggregate job started messages for some interval. + * I.e. setting this value to 2 means + * "wait first 2 minutes and send all notification only if 2 minutes passed or lst build had started already" + * + * @param aggregateJobStartedInterval time to wait builds before sending start messages + */ + public void setAggregateJobStartedInterval(Integer aggregateJobStartedInterval) { + this.aggregateJobStartedInterval = aggregateJobStartedInterval; + } + + /** + * Aggregate job started messages for some interval. + * I.e. "10" means + * "first wait 10 seconds and send all notification only if 10 seconds passed or lst build had started already" + * + * @return aggregation interval + */ + public int getAggregateJobStartedInterval() { + return aggregateJobStartedInterval; + } } diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/GerritNotifier.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/GerritNotifier.java index 75dec9ad4..026d50e0f 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/GerritNotifier.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/GerritNotifier.java @@ -37,6 +37,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + /** * Start position that notifies Gerrit of events. * @author Robert Sandell <robert.sandell@sonyericsson.com> @@ -70,18 +72,24 @@ public GerritNotifier(IGerritHudsonTriggerConfig config, GerritCmdRunner cmdRunn /** * Generates the build-started command based on configured templates and build-values and sends it to Gerrit. - * @param build the build. + * @param builds the builds. * @param taskListener the taskListener. * @param event the event. * @param stats the stats. */ - public void buildStarted(Run build, TaskListener taskListener, - GerritTriggeredEvent event, BuildsStartedStats stats) { + public void buildStarted(List builds, TaskListener taskListener, + GerritTriggeredEvent event, BuildsStartedStats stats) { try { /* Without a change, it doesn't make sense to notify gerrit */ if (event instanceof ChangeBasedEvent) { - String command = - parameterExpander.getBuildStartedCommand(build, taskListener, (ChangeBasedEvent)event, stats); + String command; + if (builds.size() == 1) { + command = parameterExpander.getBuildStartedCommand(builds.get(0), taskListener, + (ChangeBasedEvent)event, stats); + } else { + command = parameterExpander.getBuildsStartedCommand(builds, taskListener, + (ChangeBasedEvent)event, stats); + } if (command != null) { if (!command.isEmpty()) { logger.info("Notifying BuildStarted to gerrit: {}", command); diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/NotificationFactory.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/NotificationFactory.java index 667d3ce75..6ed41953d 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/NotificationFactory.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/NotificationFactory.java @@ -44,6 +44,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + /** * A factory for creating notification entities. * This factory is mainly created and used to ease unit testing. @@ -183,24 +185,24 @@ private String getServerName(GerritTriggeredEvent event) { /** * Queues a build started command on the send-command queue. * - * @param build the build. + * @param builds the build. * @param listener a listener. * @param event the event. * @param stats the started stats. * @see GerritSendCommandQueue#queue(com.sonymobile.tools.gerrit.gerritevents.workers.cmd.AbstractSendCommandJob) * @see BuildStartedCommandJob */ - public void queueBuildStarted(Run build, TaskListener listener, + public void queueBuildsStarted(List builds, TaskListener listener, GerritTriggeredEvent event, BuildsStartedStats stats) { String serverName = getServerName(event); if (serverName != null) { IGerritHudsonTriggerConfig config = getConfig(serverName); if (config != null) { if (config.isUseRestApi() && event instanceof ChangeBasedEvent) { - GerritSendCommandQueue.queue(new BuildStartedRestCommandJob(config, build, listener, + GerritSendCommandQueue.queue(new BuildStartedRestCommandJob(config, builds, listener, (ChangeBasedEvent)event, stats)); } else { - GerritSendCommandQueue.queue(new BuildStartedCommandJob(config, build, listener, event, stats)); + GerritSendCommandQueue.queue(new BuildStartedCommandJob(config, builds, listener, event, stats)); } } else { logger.warn("Nothing queued since there is no configuration for serverName: {}", serverName); diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpander.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpander.java index a938b0301..c68028498 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpander.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpander.java @@ -41,10 +41,12 @@ import hudson.model.Run; import hudson.model.TaskListener; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import jenkins.model.Jenkins; @@ -105,7 +107,7 @@ public String getBuildStartedCommand(Run r, TaskListener taskListener, GerritTrigger trigger = GerritTrigger.getTrigger(r.getParent()); String gerritCmd = config.getGerritCmdBuildStarted(); - Map parameters = createStandardParameters(r, event, + Map parameters = createStandardParameters(event, getBuildStartedCodeReviewValue(r), getBuildStartedVerifiedValue(r), Notify.ALL.name()); @@ -127,11 +129,76 @@ public String getBuildStartedCommand(Run r, TaskListener taskListener, } } + parameters.put("BUILDURL", jenkins.getRootUrl() + r.getUrl()); parameters.put("STARTED_STATS", startedStats.toString()); return expandParameters(gerritCmd, r, taskListener, parameters); } + /** + * Gets the expanded string to send to Gerrit for a build-started event. + * @param runs the builds. + * @param taskListener the taskListener. + * @param event the event. + * @param stats the statistics. + * @return the "expanded" command string. + */ + public String getBuildsStartedCommand(List runs, TaskListener taskListener, + ChangeBasedEvent event, BuildsStartedStats stats) { + + String gerritCmd = config.getGerritCmdBuildStarted(); + + Integer minCodeReview = Integer.MAX_VALUE; + Integer minVerified = Integer.MAX_VALUE; + + StringBuilder aggregatedBuilds = new StringBuilder(); + + int offset = runs.size() - 1; + + for (Run r : runs) { + GerritTrigger trigger = GerritTrigger.getTrigger(r.getParent()); + + minCodeReview = Math.min(minCodeReview, getBuildStartedCodeReviewValue(r)); + minVerified = Math.min(minVerified, getBuildStartedVerifiedValue(r)); + + aggregatedBuilds.append("\n\n"); + + String buildStartMessage = trigger.getBuildStartMessage(); + if (buildStartMessage != null && !buildStartMessage.isEmpty()) { + aggregatedBuilds.append("\n\n").append(buildStartMessage); + } + + String buildUrl = jenkins.getRootUrl() + r.getUrl(); + aggregatedBuilds.append(buildUrl); + + if (stats.getTotalBuildsToStart() > 1) { + aggregatedBuilds.append(" "); + aggregatedBuilds.append(stats.stringWithOffset(offset)); + aggregatedBuilds.append(" "); + offset--; + } + + if (config.isEnablePluginMessages()) { + for (GerritMessageProvider messageProvider : emptyIfNull(GerritMessageProvider.all())) { + String extensionMessage = messageProvider.getBuildStartedMessage(r); + if (extensionMessage != null) { + aggregatedBuilds.append("\n\n").append(extensionMessage); + } + } + } + } + + Map parameters = createStandardParameters(event, + minCodeReview, + minVerified, + Notify.ALL.name()); + + parameters.put("BUILDURL", ""); + parameters.put("STARTED_STATS", aggregatedBuilds.toString()); + + return expandParameters(gerritCmd, runs.get(0), taskListener, parameters); + } + /** * Helper for ensuring no NPEs when iterating iterables. * @@ -213,14 +280,13 @@ private Integer getBuildStartedCodeReviewValue(Run r) { *
  • CODE_REVIEW: The code review vote.
  • *
  • NOTIFICATION_LEVEL: The notification level.
  • * - * @param r the build. * @param gerritEvent the event. * @param codeReview the code review vote. * @param verified the verified vote. * @param notifyLevel the notify level. * @return the parameters and their values. */ - private Map createStandardParameters(Run r, GerritTriggeredEvent gerritEvent, + private Map createStandardParameters(GerritTriggeredEvent gerritEvent, Integer codeReview, Integer verified, String notifyLevel) { // VERIFIED CODE_REVIEW Map map = new HashMap(DEFAULT_PARAMETERS_COUNT); @@ -239,9 +305,7 @@ private Map createStandardParameters(Run r, GerritTriggeredEvent map.put("REFSPEC", StringUtil.makeRefSpec(event)); } } - if (r != null) { - map.put("BUILDURL", jenkins.getRootUrl() + r.getUrl()); - } + map.put("VERIFIED", String.valueOf(verified)); map.put("CODE_REVIEW", String.valueOf(codeReview)); map.put("NOTIFICATION_LEVEL", notifyLevel); @@ -404,6 +468,24 @@ public Integer getMinimumVerifiedValue(MemoryImprint memoryImprint, boolean only return verified; } + /** + * Convert entries of memoryImprint object to list of builds + * + * @param memoryImprint the memory + * @return the list of run objects from memory + */ + private List fromMemoryImprintToBuilds(MemoryImprint memoryImprint) { + final List runs = new ArrayList(memoryImprint.getEntries().length); + for (Entry entry : memoryImprint.getEntries()) { + if (entry == null || entry.getBuild() == null) { + continue; + } + runs.add(entry.getBuild()); + } + + return runs; + } + /** * Returns the minimum code review value for the build results in the memory. * If no builds have contributed to code review value, this method returns null @@ -449,21 +531,30 @@ public Integer getMinimumCodeReviewValue(MemoryImprint memoryImprint, boolean on * @return the highest configured notification level. */ public Notify getHighestNotificationLevel(MemoryImprint memoryImprint, boolean onlyBuilt) { + return getHighestNotificationLevel(fromMemoryImprintToBuilds(memoryImprint), onlyBuilt); + } + + /** + * Returns the highest configured notification level. + * + * @param builds the list of builds + * @param onlyBuilt only count builds that completed (no NOT_BUILT builds) + * @return the highest configured notification level. + */ + public Notify getHighestNotificationLevel(List builds, boolean onlyBuilt) { Notify highestLevel = Notify.NONE; - for (Entry entry : memoryImprint.getEntries()) { - if (entry == null) { - continue; - } - Run build = entry.getBuild(); + + for (Run build : builds) { if (build == null) { continue; } + Result result = build.getResult(); if (onlyBuilt && result == Result.NOT_BUILT) { continue; } - GerritTrigger trigger = GerritTrigger.getTrigger(entry.getProject()); + GerritTrigger trigger = GerritTrigger.getTrigger(build.getParent()); if (trigger == null || shouldSkip(trigger.getSkipVote(), result)) { continue; } @@ -473,6 +564,7 @@ public Notify getHighestNotificationLevel(MemoryImprint memoryImprint, boolean o highestLevel = level; } } + return highestLevel; } @@ -530,7 +622,7 @@ public String getBuildCompletedCommand(MemoryImprint memoryImprint, TaskListener notifyLevel = getHighestNotificationLevel(memoryImprint, onlyCountBuilt); } - Map parameters = createStandardParameters(null, memoryImprint.getEvent(), + Map parameters = createStandardParameters(memoryImprint.getEvent(), codeReview, verified, notifyLevel.name()); // escapes ' as '"'"' in order to avoid breaking command line param // Details: http://stackoverflow.com/a/26165123/99834 @@ -668,15 +760,21 @@ public String getBuildCompletedMessage(MemoryImprint memoryImprint, TaskListener /** * Returns cover message to be send after build has been started. * - * @param build build + * @param builds build * @param listener listener * @param event event * @param stats stats * @return the message for the build started command. */ - public String getBuildStartedMessage(Run build, TaskListener listener, ChangeBasedEvent event, - BuildsStartedStats stats) { - String startedCommand = getBuildStartedCommand(build, listener, event, stats); + public String getBuildsStartedMessage(List builds, TaskListener listener, ChangeBasedEvent event, + BuildsStartedStats stats) { + String startedCommand; + if (builds.size() == 1) { + startedCommand = getBuildStartedCommand(builds.get(0), listener, event, stats); + } else { + startedCommand = getBuildsStartedCommand(builds, listener, event, stats); + } + return findMessage(startedCommand); } diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListener.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListener.java index 04a1c6eb4..146f1a9ad 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListener.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListener.java @@ -24,6 +24,8 @@ package com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier; import com.sonyericsson.hudson.plugins.gerrit.trigger.diagnostics.BuildMemoryReport; +import com.sonyericsson.hudson.plugins.gerrit.trigger.PluginImpl; +import com.sonyericsson.hudson.plugins.gerrit.trigger.config.PluginConfig; import com.sonymobile.tools.gerrit.gerritevents.dto.events.GerritTriggeredEvent; import com.sonyericsson.hudson.plugins.gerrit.trigger.events.lifecycle.GerritEventLifecycle; import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildMemory; @@ -48,7 +50,10 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import jenkins.model.Jenkins; @@ -221,19 +226,20 @@ public synchronized boolean isProjectTriggeredAndIncomplete(Job p, GerritTrigger } @Override - public synchronized void onStarted(Run r, TaskListener listener) { + public synchronized void onStarted(final Run r, final TaskListener listener) { GerritCause cause = getCause(r); logger.debug("Started. Build: {} Cause: {}", r, cause); if (cause != null) { cleanUpGerritCauses(cause, r); setThisBuild(r); - if (cause.getEvent() != null) { - if (cause.getEvent() instanceof GerritEventLifecycle) { - ((GerritEventLifecycle)cause.getEvent()).fireBuildStarted(r); + final GerritTriggeredEvent gerritEvent = cause.getEvent(); + if (gerritEvent != null) { + if (gerritEvent instanceof GerritEventLifecycle) { + ((GerritEventLifecycle)gerritEvent).fireBuildStarted(r); } } if (!cause.isSilentMode()) { - memory.started(cause.getEvent(), r); + memory.started(gerritEvent, r); updateTriggerContexts(r); GerritTrigger trigger = GerritTrigger.getTrigger(r.getParent()); boolean silentStartMode = false; @@ -241,15 +247,87 @@ public synchronized void onStarted(Run r, TaskListener listener) { silentStartMode = trigger.isSilentStartMode(); } if (!silentStartMode) { - BuildsStartedStats stats = memory.getBuildsStartedStats(cause.getEvent()); - NotificationFactory.getInstance().queueBuildStarted(r, listener, cause.getEvent(), stats); + final BuildsStartedStats stats = memory.getBuildsStartedStats(gerritEvent); + long delay = timeToSendNotification(trigger); + + if (delay == 0) { + final List builds = Collections.singletonList(r); + NotificationFactory.getInstance().queueBuildsStarted(builds, listener, gerritEvent, stats); + } else { + logger.info("Schedule notification for [{}].", cause); + jenkins.util.Timer.get().schedule(new Runnable() { + @Override + public void run() { + sendNotification(gerritEvent, listener); + } + }, delay, TimeUnit.SECONDS); + } } } logger.info("Gerrit build [{}] Started for cause: [{}].", r, cause); - logger.info("MemoryStatus:\n{}", memory.getStatusReport(cause.getEvent())); + logger.info("MemoryStatus:\n{}", memory.getStatusReport(gerritEvent)); + } + } + + + /** + * Sends notification that build started + * @param event The event that triggered this build + * @param listener The build listener + */ + private synchronized void sendNotification(GerritTriggeredEvent event, TaskListener listener) { + final List buildsToNotify = new ArrayList(); + + BuildMemory.MemoryImprint memoryImprint = memory.getMemoryImprint(event); + + if (memoryImprint == null) { + logger.info("Gerrit event [{}] was forgotten already. Can't send notification", event); + return; + } + + for (BuildMemory.MemoryImprint.Entry entry : memoryImprint.getEntries()) { + if (!entry.isNotified()) { + Run run = entry.getBuild(); + if (run != null) { + buildsToNotify.add(run); + entry.setNotified(true); + } + } + } + + if (!buildsToNotify.isEmpty()) { + final BuildsStartedStats stats = memory.getBuildsStartedStats(event); + NotificationFactory.getInstance().queueBuildsStarted(buildsToNotify, listener, event, stats); + logger.info("queue notification [{}] for [{}]", event, buildsToNotify); + } else { + logger.info("Nothing to notify for [{}]", event); } } + + /** + * Returns time then build must be notified + * @param trigger the Gerrit Trigger which is being checked + * @return time then build must be notified + */ + private long timeToSendNotification(final GerritTrigger trigger) { + if (trigger == null) { + return 0; + } + + PluginImpl plugin = PluginImpl.getInstance(); + if (plugin == null) { + return 0; + } + + PluginConfig config = plugin.getPluginConfig(); + if (config == null) { + return 0; + } + + return config.getAggregateJobStartedInterval(); + } + /** * Updates the {@link com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.TriggerContext}s for all the * {@link GerritCause}s in the build. diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/rest/BuildStartedRestCommandJob.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/rest/BuildStartedRestCommandJob.java index 922f5fad8..010086021 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/rest/BuildStartedRestCommandJob.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/rest/BuildStartedRestCommandJob.java @@ -29,19 +29,20 @@ import com.sonyericsson.hudson.plugins.gerrit.trigger.config.IGerritHudsonTriggerConfig; import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.ParameterExpander; import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildsStartedStats; -import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger; import com.sonymobile.tools.gerrit.gerritevents.dto.rest.Notify; import com.sonymobile.tools.gerrit.gerritevents.dto.rest.ReviewInput; import hudson.model.Run; import hudson.model.TaskListener; +import java.util.List; + /** * A job for the {@link com.sonymobile.tools.gerrit.gerritevents.GerritSendCommandQueue} that * sends a build started message. */ public class BuildStartedRestCommandJob extends AbstractRestCommandJob { - private final Run build; + private final List builds; private final BuildsStartedStats stats; private final TaskListener listener; private final ParameterExpander parameterExpander; @@ -50,16 +51,16 @@ public class BuildStartedRestCommandJob extends AbstractRestCommandJob { * Constructor. * * @param config config - * @param build build + * @param builds builds * @param listener listener * @param event event * @param stats stats */ - public BuildStartedRestCommandJob(IGerritHudsonTriggerConfig config, Run build, TaskListener listener, + public BuildStartedRestCommandJob(IGerritHudsonTriggerConfig config, List builds, TaskListener listener, ChangeBasedEvent event, BuildsStartedStats stats) { //CS IGNORE AvoidInlineConditionals FOR NEXT 1 LINES. REASON: Only more hard to read alternatives apply. super(config, (listener != null ? listener.getLogger() : null), event); - this.build = build; + this.builds = builds; this.stats = stats; this.listener = listener; parameterExpander = new ParameterExpander(config); @@ -72,13 +73,9 @@ public BuildStartedRestCommandJob(IGerritHudsonTriggerConfig config, Run build, */ @Override protected ReviewInput createReview() { - String message = parameterExpander.getBuildStartedMessage(build, listener, event, stats); - Notify notificationLevel = Notify.ALL; - GerritTrigger trigger = GerritTrigger.getTrigger(build.getParent()); - if (trigger != null) { - notificationLevel = parameterExpander.getNotificationLevel(trigger); - } - return new ReviewInput(message).setNotify(notificationLevel).setTag(Constants.TAG_VALUE); + String message = parameterExpander.getBuildsStartedMessage(builds, listener, event, stats); + Notify notificationLevel = parameterExpander.getHighestNotificationLevel(builds, false); + return new ReviewInput(message).setNotify(notificationLevel); } } diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/ssh/BuildStartedCommandJob.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/ssh/BuildStartedCommandJob.java index a83e4381e..0a1aca139 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/ssh/BuildStartedCommandJob.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/job/ssh/BuildStartedCommandJob.java @@ -39,6 +39,8 @@ import hudson.model.TaskListener; import hudson.security.ACL; +import java.util.List; + /** * A send-command-job that calculates and sends the build started command. * @@ -46,7 +48,7 @@ */ public class BuildStartedCommandJob extends AbstractSendCommandJob { - private Run build; + private List builds; private TaskListener taskListener; private GerritTriggeredEvent event; private BuildsStartedStats stats; @@ -55,17 +57,17 @@ public class BuildStartedCommandJob extends AbstractSendCommandJob { * Standard constructor with all the required data for the job. * * @param config the config. - * @param build a build. + * @param builds a build. * @param taskListener a listener. * @param event the event. - * @param stats the stats. - * @see GerritNotifier#buildStarted(Run, TaskListener, GerritTriggeredEvent, BuildsStartedStats) + * @param stats the stats. FIXME (first argument has a wrong type) + * @see GerritNotifier#buildStarted(List, TaskListener, GerritTriggeredEvent, BuildsStartedStats) */ - public BuildStartedCommandJob(IGerritHudsonTriggerConfig config, Run build, + public BuildStartedCommandJob(IGerritHudsonTriggerConfig config, List builds, TaskListener taskListener, GerritTriggeredEvent event, BuildsStartedStats stats) { super(config); - this.build = build; + this.builds = builds; this.taskListener = taskListener; this.event = event; this.stats = stats; @@ -77,7 +79,7 @@ public void run() { try { GerritNotifier notifier = NotificationFactory.getInstance() .createGerritNotifier((IGerritHudsonTriggerConfig)getConfig(), this); - notifier.buildStarted(build, taskListener, event, stats); + notifier.buildStarted(builds, taskListener, event, stats); } finally { SecurityContextHolder.setContext(old); } diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildMemory.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildMemory.java index 2ba9865a0..ad7064052 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildMemory.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildMemory.java @@ -171,7 +171,7 @@ public synchronized void completed(GerritTriggeredEvent event, Run build) { pb = new MemoryImprint(event); memory.put(event, pb); } - pb.set(build.getParent(), build, true); + pb.set(build.getParent(), build, true, false); } /** @@ -226,7 +226,7 @@ public synchronized void retriggered( if (otherBuilds != null) { //It is a new memory so it wasn't building, let's populate with old build info for (Run build : otherBuilds) { - pb.set(build.getParent(), build, !build.isBuilding()); + pb.set(build.getParent(), build, !build.isBuilding(), true); } } } @@ -567,6 +567,7 @@ protected synchronized void reset(Job project) { } else { entry.setBuild(null); entry.setBuildCompleted(false); + entry.setNotified(false); } } @@ -576,18 +577,21 @@ protected synchronized void reset(Job project) { * @param project the project * @param build the build * @param buildCompleted if the build is finished. + * @param buildWasNotified if the notification for build was sent. */ - private synchronized void set(Job project, Run build, boolean buildCompleted) { + private synchronized void set(Job project, Run build, boolean buildCompleted, boolean buildWasNotified) { Entry entry = getEntry(project); if (entry == null) { entry = new Entry(project, build); - entry.setBuildCompleted(buildCompleted); list.add(entry); } else { if (entry.getBuild() == null) { entry.setBuild(build); } - entry.setBuildCompleted(buildCompleted); + } + entry.setBuildCompleted(buildCompleted); + if (buildWasNotified) { + entry.setNotified(true); } } @@ -642,6 +646,7 @@ public synchronized String getStatusReport() { str.append("XX: NULL"); } str.append("] Completed: ").append(entry.isBuildCompleted()); + str.append(" Notified: ").append(entry.isNotified()); } else { str.append(" Project/Build: MISSING PROJECT!"); } @@ -825,7 +830,7 @@ public synchronized boolean wereAllBuildsNotBuilt() { */ public static class Entry implements Cloneable { - private String project; + private final String project; private String build; private boolean buildCompleted; private boolean cancelled; @@ -834,6 +839,7 @@ public static class Entry implements Cloneable { private final long triggeredTimestamp; private Long completedTimestamp = null; private Long startedTimestamp = null; + private boolean notified; /** * Constructor. @@ -846,7 +852,8 @@ private Entry(Job project, Run build) { this.build = build.getId(); this.startedTimestamp = System.currentTimeMillis(); this.triggeredTimestamp = System.currentTimeMillis(); - buildCompleted = false; + this.buildCompleted = false; + this.notified = false; } /** @@ -856,9 +863,10 @@ private Entry(Job project, Run build) { */ private Entry(Job project) { this.project = project.getFullName(); - buildCompleted = false; cancelled = false; this.triggeredTimestamp = System.currentTimeMillis(); + this.buildCompleted = false; + this.notified = false; } /** @@ -877,6 +885,7 @@ public Entry(Entry copy) { this.startedTimestamp = copy.startedTimestamp; this.customUrl = copy.customUrl; this.cancelled = copy.cancelled; + this.notified = false; } @Override @@ -884,6 +893,24 @@ public Entry clone() { return new Entry(this); } + /** + * If the notification was sent. + * + * @return true if the notification was sent. + */ + public synchronized boolean isNotified() { + return notified; + } + + /** + * If the notification was sent. + * + * @param notified true if the notification was sent. + */ + public synchronized void setNotified(boolean notified) { + this.notified = notified; + } + /** * The Project. * diff --git a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildsStartedStats.java b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildsStartedStats.java index 3a8e57ea6..4d57f3f9d 100644 --- a/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildsStartedStats.java +++ b/src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/model/BuildsStartedStats.java @@ -32,9 +32,9 @@ * @author Robert Sandell <robert.sandell@sonyericsson.com> */ public class BuildsStartedStats { - private GerritTriggeredEvent event; - private int totalBuildsToStart; - private int startedBuilds; + private final GerritTriggeredEvent event; + private final int totalBuildsToStart; + private final int startedBuilds; /** * The Constructor. @@ -56,14 +56,6 @@ public GerritTriggeredEvent getEvent() { return event; } - /** - * The event that started the build(s). - * @param event the event that started the build(s). - */ - public void setEvent(GerritTriggeredEvent event) { - this.event = event; - } - /** * The amount of builds that have been started so far. * @return the amount of builds that have been started so far. @@ -72,14 +64,6 @@ public int getStartedBuilds() { return startedBuilds; } - /** - * The amount of builds that have been started so far. - * @param startedBuilds the amount of builds that have been started so far. - */ - void setStartedBuilds(int startedBuilds) { - this.startedBuilds = startedBuilds; - } - /** * The total amount of builds that have been triggered. * @return the total amount of builds that have been triggered. @@ -88,20 +72,28 @@ public int getTotalBuildsToStart() { return totalBuildsToStart; } - /** - * The total amount of builds that have been triggered. - * @param totalBuildsToStart the total amount of builds that have been triggered. - */ - void setTotalBuildsToStart(int totalBuildsToStart) { - this.totalBuildsToStart = totalBuildsToStart; - } - @Override public String toString() { - StringBuilder str = new StringBuilder("("); - str.append(getStartedBuilds()).append("/").append(getTotalBuildsToStart()).append(")"); - return str.toString(); + return getStats(0); } + /** + * Returns build started stats with the offset. + * + * @param offset the number of builds should off set + * @return the string with build started stats with the offset + */ + public String stringWithOffset(int offset) { + return getStats(offset); + } + /** + * Returns build started stats with the offset. + * + * @param offset the number of builds should off set + * @return the string with build started stats with the offset + */ + private String getStats(int offset) { + return "(" + (startedBuilds - offset) + "/" + getTotalBuildsToStart() + ")"; + } } diff --git a/src/main/resources/com/sonyericsson/hudson/plugins/gerrit/trigger/GerritManagement/index.jelly b/src/main/resources/com/sonyericsson/hudson/plugins/gerrit/trigger/GerritManagement/index.jelly index cda922897..099c00cfd 100644 --- a/src/main/resources/com/sonyericsson/hudson/plugins/gerrit/trigger/GerritManagement/index.jelly +++ b/src/main/resources/com/sonyericsson/hudson/plugins/gerrit/trigger/GerritManagement/index.jelly @@ -67,6 +67,12 @@ value="${it.pluginConfig.replicationCacheExpirationInMinutes}" default="${com.sonyericsson.hudson.plugins.gerrit.trigger.replication.ReplicationCache.DEFAULT_EXPIRATION_IN_MINUTES}"/> + + + diff --git a/src/main/webapp/help-AggregateJobStartedInterval.html b/src/main/webapp/help-AggregateJobStartedInterval.html new file mode 100644 index 000000000..0e24957b5 --- /dev/null +++ b/src/main/webapp/help-AggregateJobStartedInterval.html @@ -0,0 +1,7 @@ +Aggregation interval for sending job started events in seconds
    +
    +If value is bigger than 0, user will receive message that job started only after X seconds. +If some job started after X seconds only, message will be sent for this job immediately. +
    +
    +Default value is 0 - no aggregation each event is sent separately. diff --git a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpanderTest.java b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpanderTest.java index 9845cbe52..ae175d90d 100644 --- a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpanderTest.java +++ b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ParameterExpanderTest.java @@ -29,6 +29,7 @@ import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildsStartedStats; import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger; import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.SkipVote; +import com.sonyericsson.hudson.plugins.gerrit.trigger.mock.MockGerritHudsonTriggerConfig; import com.sonyericsson.hudson.plugins.gerrit.trigger.mock.Setup; import com.sonyericsson.hudson.plugins.gerrit.trigger.utils.StringUtil; import com.sonymobile.tools.gerrit.gerritevents.dto.events.ChangeBasedEvent; @@ -43,6 +44,7 @@ import hudson.model.AbstractProject; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -125,20 +127,96 @@ public void testGetBuildStartedCommand() throws Exception { String result = instance.getBuildStartedCommand(r, taskListener, event, stats); System.out.println("result: " + result); + + String expectedUrl = Jenkins.getInstance().getRootUrl() + r.getUrl(); assertTrue("Missing START_MESSAGE_VAL from getBuildStartMessage()", result.indexOf("START_MESSAGE_VAL") >= 0); - assertTrue("Missing CHANGE_ID", result.indexOf("CHANGE_ID=Iddaaddaa123456789") >= 0); - assertTrue("Missing PATCHSET", result.indexOf("PATCHSET=1") >= 0); - assertTrue("Missing VERIFIED", result.indexOf("VERIFIED=1") >= 0); - assertTrue("Missing CODEREVIEW", result.indexOf("CODEREVIEW=32") >= 0); - assertTrue("Missing NOTIFICATION_LEVEL", result.indexOf("NOTIFICATION_LEVEL=ALL") >= 0); - assertTrue("Missing REFSPEC", result.indexOf("REFSPEC=" + expectedRefSpec) >= 0); - assertTrue("Missing ENV_BRANCH", result.indexOf("ENV_BRANCH=branch") >= 0); - assertTrue("Missing ENV_CHANGE", result.indexOf("ENV_CHANGE=1000") >= 0); - assertTrue("Missing ENV_REFSPEC", result.indexOf("ENV_REFSPEC=" + expectedRefSpec) >= 0); - assertTrue("Missing ENV_CHANGEURL", result.indexOf("ENV_CHANGEURL=http://gerrit/1000") >= 0); - assertTrue("Missing CUSTOM_MESSAGE", result.indexOf("CUSTOM_MESSAGE_BUILD_STARTED") >= 0); - assertTrue("Newlines are stripped", result.indexOf("Message\nwith newline") >= 0); + assertTrue("Missing CHANGE_ID", result.contains("CHANGE_ID=Iddaaddaa123456789")); + assertTrue("Missing PATCHSET", result.contains("PATCHSET=1")); + assertTrue("Missing VERIFIED", result.contains("VERIFIED=1")); + assertTrue("Missing CODEREVIEW", result.contains("CODEREVIEW=32")); + assertTrue("Missing NOTIFICATION_LEVEL", result.contains("NOTIFICATION_LEVEL=ALL")); + assertTrue("Missing REFSPEC", result.contains("REFSPEC=" + expectedRefSpec)); + assertTrue("Missing ENV_BRANCH", result.contains("ENV_BRANCH=branch")); + assertTrue("Missing ENV_CHANGE", result.contains("ENV_CHANGE=1000")); + assertTrue("Missing ENV_REFSPEC", result.contains("ENV_REFSPEC=" + expectedRefSpec)); + assertTrue("Missing ENV_CHANGEURL", result.contains("ENV_CHANGEURL=http://gerrit/1000")); + assertTrue("Missing CUSTOM_MESSAGE", result.contains("CUSTOM_MESSAGE_BUILD_STARTED")); + assertTrue("Missing BUILDURL", result.contains("BUILDURL=" + expectedUrl)); + assertTrue("Missing STARTED_STATS", result.contains("STARTED_STATS=(1/3)")); + assertTrue("Newlines are stripped", result.contains("Message\nwith newline")); + } + + /** + * test. + * @throws Exception Exception + */ + @Test + public void testGetBuildsStartedCommand() throws Exception { + TaskListener taskListener = mock(TaskListener.class); + + GerritTrigger trigger = mock(GerritTrigger.class); + when(trigger.getGerritBuildStartedVerifiedValue()).thenReturn(null); + when(trigger.getGerritBuildStartedCodeReviewValue()).thenReturn(32); + AbstractProject project = mock(AbstractProject.class); + Setup.setTrigger(trigger, project); + + GerritTrigger secondTrigger = mock(GerritTrigger.class); + when(secondTrigger.getGerritBuildStartedVerifiedValue()).thenReturn(-2); + when(secondTrigger.getGerritBuildStartedCodeReviewValue()).thenReturn(-25); + AbstractProject secondProject = mock(AbstractProject.class); + Setup.setTrigger(secondTrigger, secondProject); + + AbstractBuild build = Setup.createBuild(project, taskListener, Setup.createEnvVars()); + AbstractBuild secondBuild = Setup.createBuild(secondProject, taskListener, Setup.createEnvVars()); + + PatchsetCreated event = Setup.createPatchsetCreated(); + BuildsStartedStats stats = new BuildsStartedStats(event, 2, 2); + MockGerritHudsonTriggerConfig config = Setup.createConfig(); + + config.setGerritCmdBuildStarted("CHANGE=" + + " CHANGE_ID=" + + " PATCHSET=" + + " VERIFIED=" + + " CODEREVIEW=" + + " NOTIFICATION_LEVEL=" + + " REFSPEC= MSG=I started a build." + + " STARTED_STATS=" + + " ENV_BRANCH=$BRANCH" + + " ENV_CHANGE=$CHANGE" + + " ENV_PATCHSET=$PATCHSET" + + " ENV_REFSPEC=$REFSPEC" + + " ENV_CHANGEURL=$CHANGE_URL"); + + PowerMockito.mockStatic(GerritMessageProvider.class); + List messageProviderExtensionList = new LinkedList(); + messageProviderExtensionList.add(new GerritMessageProviderExtension()); + messageProviderExtensionList.add(new GerritMessageProviderExtensionReturnNull()); + when(GerritMessageProvider.all()).thenReturn(messageProviderExtensionList); + + ParameterExpander instance = new ParameterExpander(config, jenkins); + + final String expectedRefSpec = StringUtil.makeRefSpec(event); + + List builds = new ArrayList(2); + builds.add(build); + builds.add(secondBuild); + + String result = instance.getBuildsStartedCommand(builds, taskListener, event, stats); + System.out.println("result: " + result); + + assertTrue("Missing CHANGE_ID", result.contains("CHANGE_ID=Iddaaddaa123456789")); + assertTrue("BUILDURL must be erased", !result.contains("BUILDURL=")); + assertTrue("Missing PATCHSET", result.contains("PATCHSET=1")); + assertTrue("Missing VERIFIED", result.contains("VERIFIED=-2")); + assertTrue("Missing CODEREVIEW", result.contains("CODEREVIEW=-25")); + assertTrue("Missing NOTIFICATION_LEVEL", result.contains("NOTIFICATION_LEVEL=ALL")); + assertTrue("Missing REFSPEC", result.contains("REFSPEC=" + expectedRefSpec)); + assertTrue("Missing ENV_BRANCH", result.contains("ENV_BRANCH=branch")); + assertTrue("Missing ENV_CHANGE", result.contains("ENV_CHANGE=1000")); + assertTrue("Missing ENV_REFSPEC", result.contains("ENV_REFSPEC=" + expectedRefSpec)); + assertTrue("Missing ENV_CHANGEURL", result.contains("ENV_CHANGEURL=http://gerrit/1000")); + assertTrue("Missing CUSTOM_MESSAGE", result.contains("CUSTOM_MESSAGE_BUILD_STARTED")); } /** @@ -299,7 +377,7 @@ public void testGetMinimumCodeReviewValueForOneJobOverridenBuildSuccessful() { entries[0] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.SUCCESS); trigger = mock(GerritTrigger.class); - when(trigger.getGerritBuildSuccessfulCodeReviewValue()).thenReturn(Integer.valueOf(2)); + when(trigger.getGerritBuildSuccessfulCodeReviewValue()).thenReturn(2); entries[1] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.SUCCESS); when(memoryImprint.getEntries()).thenReturn(entries); @@ -329,7 +407,7 @@ public void testGetMinimumCodeReviewValueForOneJobOverridenBuildFailed() { entries[0] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.FAILURE); trigger = mock(GerritTrigger.class); - when(trigger.getGerritBuildFailedCodeReviewValue()).thenReturn(Integer.valueOf(-2)); + when(trigger.getGerritBuildFailedCodeReviewValue()).thenReturn(-2); entries[1] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.FAILURE); when(memoryImprint.getEntries()).thenReturn(entries); @@ -359,7 +437,7 @@ public void testGetMinimumCodeReviewValueForOneJobOverridenMixed() { entries[0] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.FAILURE); trigger = mock(GerritTrigger.class); - when(trigger.getGerritBuildSuccessfulCodeReviewValue()).thenReturn(Integer.valueOf(2)); + when(trigger.getGerritBuildSuccessfulCodeReviewValue()).thenReturn(2); entries[1] = Setup.createAndSetupMemoryImprintEntry(trigger, Result.SUCCESS); when(memoryImprint.getEntries()).thenReturn(entries); @@ -521,7 +599,7 @@ public void tryGetBuildCompletedCommandSuccessfulEvent(String customUrl, String throws IOException, InterruptedException { tryGetBuildCompletedCommandEventWithResults(customUrl, new String[] {expectedBuildsStats}, new Result[] {Result.SUCCESS}, "'Your friendly butler says OK.", - Setup.createChangeRestored(), null, null); + event, expectedVerifiedVote, expectedCodeReviewVote); } /** @@ -669,7 +747,7 @@ public void tryBuildStatsFailureCommand(String unsuccessfulMessage, String expec String result = instance.getBuildCompletedCommand(memoryImprint, taskListener); System.out.println("Result: " + result); - assertTrue("Missing BS", result.indexOf(" BS=" + expectedBuildStats) >= 0); + assertTrue("Missing BS", result.contains(" BS=" + expectedBuildStats)); } /** @@ -722,7 +800,7 @@ public void getBuildStatsFailureCommandWithNullsForCodeReviewValues() throws Exc String result = instance.getBuildCompletedCommand(memoryImprint, taskListener); System.out.println("Result: " + result); - assertTrue("Missing Build has Failed", result.indexOf("This Build has Failed") >= 0); + assertTrue("Missing Build has Failed", result.contains("This Build has Failed")); } /** @@ -808,7 +886,7 @@ public static final class SubstringMatcher extends TypeSafeMatcher { * * @param substrings the substrings to check */ - public SubstringMatcher(final String... substrings) { + SubstringMatcher(final String... substrings) { this.substrings = substrings; } diff --git a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListenerTest.java b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListenerTest.java index 2188f9f2b..b77d39d2c 100644 --- a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListenerTest.java +++ b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/gerritnotifier/ToGerritRunListenerTest.java @@ -25,6 +25,8 @@ package com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier; +import com.sonyericsson.hudson.plugins.gerrit.trigger.config.PluginConfig; +import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger; import com.sonymobile.tools.gerrit.gerritevents.GerritCmdRunner; import com.sonymobile.tools.gerrit.gerritevents.dto.events.PatchsetCreated; import com.sonyericsson.hudson.plugins.gerrit.trigger.GerritServer; @@ -44,7 +46,9 @@ import hudson.model.CauseAction; import hudson.model.Job; import hudson.model.Result; +import hudson.model.Run; import hudson.model.TaskListener; +import hudson.triggers.Trigger; import jenkins.model.Jenkins; import org.junit.Before; import org.junit.Test; @@ -55,10 +59,11 @@ import org.powermock.reflect.Whitebox; import java.io.File; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -68,6 +73,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.timeout; import static org.powermock.api.mockito.PowerMockito.doReturn; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; @@ -87,16 +93,17 @@ AbstractProject.class, NotificationFactory.class, PluginImpl.class, - ToGerritRunListener.class, - + ToGerritRunListener.class }) public class ToGerritRunListenerTest { private GerritNotifier mockNotifier; private NotificationFactory mockNotificationFactory; - private PluginImpl plugin; - private GerritServer server; private Jenkins jenkins; + private PluginConfig config; + private GerritTrigger trigger; + + private static final int TIMEOUT = 5000; /** * Creates a new static mock of GerritNotifier before each test. @@ -112,15 +119,19 @@ public void setup() throws Exception { mockStatic(NotificationFactory.class); mockStatic(PluginImpl.class); mockNotificationFactory = mock(NotificationFactory.class); - plugin = mock(PluginImpl.class); + PluginImpl plugin = mock(PluginImpl.class); + trigger = mock(GerritTrigger.class); mockNotifier = mock(GerritNotifier.class); - server = mock(GerritServer.class); + GerritServer server = mock(GerritServer.class); doReturn(mockNotifier).when(mockNotificationFactory) .createGerritNotifier(any(GerritCmdRunner.class), any(String.class)); when(NotificationFactory.class, "getInstance").thenReturn(mockNotificationFactory); when(PluginImpl.class, "getInstance").thenReturn(plugin); when(plugin.getServer(PluginImpl.DEFAULT_SERVER_NAME)).thenReturn(server); when(server.getName()).thenReturn(PluginImpl.DEFAULT_SERVER_NAME); + + config = mock(PluginConfig.class); + when(plugin.getPluginConfig()).thenReturn(config); } /** @@ -133,6 +144,7 @@ public void setup() throws Exception { private AbstractProject mockProject(String fullName) throws Exception { AbstractProject project = PowerMockito.mock(AbstractProject.class); doReturn(fullName).when(project).getFullName(); + doReturn(fullName).when(project).getName(); when(jenkins.getItemByFullName(eq(fullName), same(AbstractProject.class))).thenReturn(project); when(jenkins.getItemByFullName(eq(fullName), same(Job.class))).thenReturn(project); return project; @@ -144,11 +156,34 @@ private AbstractProject mockProject(String fullName) throws Exception { * * @param projectFullName the project's name * @param buildNumber the buildNumber. + * @param event the gerrit event + * @param isBuilding is build in progress. * @return a mock. * @throws Exception if so. */ - private AbstractBuild mockBuild(String projectFullName, int buildNumber) throws Exception { + private Run mockBuild(String projectFullName, int buildNumber, + ManualPatchsetCreated event, boolean isBuilding) throws Exception { + Run build = mockBuild(projectFullName, buildNumber); + GerritCause cause = new GerritCause(event, false); + when(build.getCause(GerritCause.class)).thenReturn(cause); + CauseAction causeAction = mock(CauseAction.class); + when(causeAction.getCauses()).thenReturn(Collections.singletonList(cause)); + when(build.getAction(CauseAction.class)).thenReturn(causeAction); + when(build.isBuilding()).thenReturn(isBuilding); + return build; + } + + /** + * Returns a mocked AbstractBuild. The build will contain a mocked AbstractProject with the provided name and have + * the provided buildNumber. + * + * @param projectFullName the project's name + * @param buildNumber the buildNumber. + * @return a mock. + * @throws Exception if so. + */ + private AbstractBuild mockBuild(String projectFullName, int buildNumber) throws Exception { AbstractProject project = mockProject(projectFullName); String buildId = projectFullName + "#" + buildNumber; @@ -160,13 +195,17 @@ private AbstractBuild mockBuild(String projectFullName, int buildNumber) throws when(build.getNumber()).thenReturn(buildNumber); EnvVars envVars = Setup.createEnvVars(); - doReturn(envVars).when(build).getEnvironment(); doReturn(envVars).when(build).getEnvironment(any(TaskListener.class)); Map buildVarsMap = new HashMap(); buildVarsMap.put("BUILD_NUM", Integer.toString(buildNumber)); when(build.getBuildVariables()).thenReturn(buildVarsMap); + when(build.toString()).thenReturn(buildId); + + Map triggerMap = new HashMap(); + triggerMap.put("Trigger", trigger); + when(project.getTriggers()).thenReturn(triggerMap); return build; } @@ -178,14 +217,8 @@ private AbstractBuild mockBuild(String projectFullName, int buildNumber) throws */ @Test public void testOnCompleted() throws Exception { - AbstractBuild build = mockBuild("projectX", 2); - ManualPatchsetCreated event = Setup.createManualPatchsetCreated(); - event = spy(event); - GerritCause cause = new GerritCause(event, false); - when(build.getCause(GerritCause.class)).thenReturn(cause); - CauseAction causeAction = mock(CauseAction.class); - when(causeAction.getCauses()).thenReturn(Collections.singletonList(cause)); - when(build.getAction(CauseAction.class)).thenReturn(causeAction); + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + Run build = mockBuild("projectX", 2, event, false); when(build.getResult()).thenReturn(Result.SUCCESS); ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); @@ -310,24 +343,238 @@ public void testObtainUnsuccessfulMessageWithMatchingFiles() throws Exception { */ @Test public void testOnStarted() throws Exception { - AbstractBuild build = mockBuild("projectX", 2); - ManualPatchsetCreated event = Setup.createManualPatchsetCreated(); - event = spy(event); - GerritCause cause = new GerritCause(event, false); - when(build.getCause(GerritCause.class)).thenReturn(cause); - CauseAction causeAction = mock(CauseAction.class); - when(causeAction.getCauses()).thenReturn(Collections.singletonList(cause)); - when(build.getAction(CauseAction.class)).thenReturn(causeAction); + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + List runs = Collections.singletonList( + mockBuild("projectX", 2, event, true)); ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); - toGerritRunListener.onStarted(build, mock(TaskListener.class)); + startBuilds(runs, toGerritRunListener); - verify(event).fireBuildStarted(same(build)); - verify(mockNotificationFactory).queueBuildStarted(same(build), + verify(event).fireBuildStarted(same(runs.get(0))); + verify(mockNotificationFactory).queueBuildsStarted(eq(runs), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + } + + /** + * Tests {@link ToGerritRunListener#onStarted(hudson.model.Run, hudson.model.TaskListener)}. + * Two events start at same time, but first one is already completed. + * Notification is must be sent for both builds anyway. + * + * @throws Exception if so. + */ + @Test + public void testOnStartedTwoBuildsOneIsCompletedButNotNotified() throws Exception { + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + when(config.getAggregateJobStartedInterval()).thenReturn(1); + + List runs = new ArrayList(); + runs.add(mockBuild("projectX", 1, event, true)); + runs.add(mockBuild("projectY", 0, event, true)); + + ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); + + toGerritRunListener.onStarted(runs.get(0), mock(TaskListener.class)); + toGerritRunListener.onStarted(runs.get(1), mock(TaskListener.class)); + toGerritRunListener.onCompleted(runs.get(0), mock(TaskListener.class)); + + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(runs), any(TaskListener.class), same(event), any(BuildsStartedStats.class)); + verify(event).fireBuildStarted(same(runs.get(0))); + verify(event).fireBuildStarted(same(runs.get(1))); + } + + /** + * Tests {@link ToGerritRunListener#onStarted(hudson.model.Run, hudson.model.TaskListener)}. + * First build is started and completed, after that second build starts. + * Notification must be sent only for both builds separately. + * + * @throws Exception if so. + */ + @Test + public void testOnStartedTwoBuildsOneIsCompleteAndNotified() throws Exception { + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + when(config.getAggregateJobStartedInterval()).thenReturn(1); + + Run firstBuild = mockBuild("projectX", 1, event, true); + + ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); + toGerritRunListener.onStarted(firstBuild, mock(TaskListener.class)); + + verify(event).fireBuildStarted(same(firstBuild)); + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(Collections.singletonList(firstBuild)), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + + toGerritRunListener.onCompleted(firstBuild, mock(TaskListener.class)); + + Run secondBuild = mockBuild("projectY", 0, event, true); + toGerritRunListener.onStarted(secondBuild, mock(TaskListener.class)); + + verify(event).fireBuildStarted(same(secondBuild)); + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(Collections.singletonList(secondBuild)), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + } + + /** + * Tests {@link ToGerritRunListener#onStarted(hudson.model.Run, hudson.model.TaskListener)}. + * Two builds start at same time. + * Notification must be sent for both builds. + * + * @throws Exception if so. + */ + @Test + public void testOnStartedTwoBuildsWithAggregation() throws Exception { + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + + when(config.getAggregateJobStartedInterval()).thenReturn(1); + + List runs = new ArrayList(); + runs.add(mockBuild("projectX", 1, event, true)); + runs.add(mockBuild("projectY", 0, event, true)); + + ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); + + startBuilds(runs, toGerritRunListener); + + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(runs), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + verify(event).fireBuildStarted(same(runs.get(0))); + verify(event).fireBuildStarted(same(runs.get(1))); + } + + /** + * Tests {@link ToGerritRunListener#onStarted(hudson.model.Run, hudson.model.TaskListener)}. + * Two builds start at same time. After that one build is re-triggered. + * Notification must be sent for both builds at first place. + * And only for re-triggered build after that. + * + * @throws Exception if so. + */ + @Test + public void testOnStartedTwoBuildsWithAggregationAndRetriggerOne() throws Exception { + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + + List runs = new ArrayList(); + runs.add(mockBuild("projectX", 1, event, true)); + runs.add(mockBuild("projectY", 0, event, true)); + + when(config.getAggregateJobStartedInterval()).thenReturn(1); + + ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); + + startBuilds(runs, toGerritRunListener); + + // synchronisation point + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(runs), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + + completeBuilds(runs, event, toGerritRunListener); + + Run retriggeredBuild = mockBuild("projectX", 2, event, true); + toGerritRunListener.onRetriggered(retriggeredBuild.getParent(), event, + Collections.singletonList(runs.get(1))); + + toGerritRunListener.onStarted(retriggeredBuild, mock(TaskListener.class)); + + List retriggeredBuilds = Collections.singletonList(retriggeredBuild); + + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted( + eq(retriggeredBuilds), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + } + + + /** + * Tests {@link ToGerritRunListener#onStarted(hudson.model.Run, hudson.model.TaskListener)}. + * Two builds start at same time. After that both builds are re-triggered. + * Notification must be sent for both builds in both cases. + * + * @throws Exception if so. + */ + @Test + public void testOnStartedTwoBuildsWithAggregationAndRetriggerTwo() throws Exception { + ManualPatchsetCreated event = spy(Setup.createManualPatchsetCreated()); + + List runs = new ArrayList(); + runs.add(mockBuild("projectX", 1, event, true)); + runs.add(mockBuild("projectY", 0, event, true)); + + when(config.getAggregateJobStartedInterval()).thenReturn(1); + + ToGerritRunListener toGerritRunListener = new ToGerritRunListener(); + + startBuilds(runs, toGerritRunListener); + + // synchronisation point + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted(eq(runs), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + + completeBuilds(runs, event, toGerritRunListener); + + Run retriggeredBuild = mockBuild("projectX", 2, event, true); + toGerritRunListener.onRetriggered(retriggeredBuild.getParent(), event, + Collections.singletonList(runs.get(1))); + + toGerritRunListener.onStarted(retriggeredBuild, mock(TaskListener.class)); + + Run retriggeredSecondBuild = mockBuild("projectY", 1, event, true); + toGerritRunListener.onRetriggered(retriggeredSecondBuild.getParent(), event, + Collections.singletonList(retriggeredBuild)); + + toGerritRunListener.onStarted(retriggeredSecondBuild, mock(TaskListener.class)); + + List retriggeredBuilds = new ArrayList(2); + retriggeredBuilds.add(retriggeredSecondBuild); + retriggeredBuilds.add(retriggeredBuild); + + verify(mockNotificationFactory, timeout(TIMEOUT)).queueBuildsStarted( + eq(retriggeredBuilds), + any(TaskListener.class), + same(event), + any(BuildsStartedStats.class)); + } + + /** + * Start all builds from the list. + * @param runs the builds + * @param toGerritRunListener the gerrit listener + */ + private void startBuilds(List runs, ToGerritRunListener toGerritRunListener) { + for (Run run : runs) { + toGerritRunListener.onStarted(run, mock(TaskListener.class)); + } + } + + /** + * Complete all builds from the list. + * @param runs the builds + * @param event the event + * @param toGerritRunListener the gerrit listener + */ + private void completeBuilds(List runs, ManualPatchsetCreated event, ToGerritRunListener toGerritRunListener) { + for (Run build : runs) { + toGerritRunListener.onCompleted(build, mock(TaskListener.class)); + when(build.isBuilding()).thenReturn(false); + } + + toGerritRunListener.allBuildsCompleted(event, (GerritCause)runs.get(0).getCause(GerritCause.class) + , mock(TaskListener.class)); } /** diff --git a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/mock/MockGerritHudsonTriggerConfig.java b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/mock/MockGerritHudsonTriggerConfig.java index a77782d2e..a88b1e249 100644 --- a/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/mock/MockGerritHudsonTriggerConfig.java +++ b/src/test/java/com/sonyericsson/hudson/plugins/gerrit/trigger/mock/MockGerritHudsonTriggerConfig.java @@ -49,24 +49,33 @@ */ public class MockGerritHudsonTriggerConfig implements IGerritHudsonTriggerConfig { + private String cmdBuildStartedCommand = "CHANGE=" + + " CHANGE_ID=" + + " PATCHSET=" + + " VERIFIED=" + + " CODEREVIEW=" + + " NOTIFICATION_LEVEL=" + + " REFSPEC= MSG=I started a build." + + " BUILDURL=" + + " STARTED_STATS=" + + " ENV_BRANCH=$BRANCH" + + " ENV_CHANGE=$CHANGE" + + " ENV_PATCHSET=$PATCHSET" + + " ENV_REFSPEC=$REFSPEC" + + " ENV_CHANGEURL=$CHANGE_URL" + + " Message\nwith newline"; @Override public String getGerritCmdBuildStarted() { - return "CHANGE=" - + " CHANGE_ID=" - + " PATCHSET=" - + " VERIFIED=" - + " CODEREVIEW=" - + " NOTIFICATION_LEVEL=" - + " REFSPEC= MSG=I started a build." - + " BUILDURL=" - + " STARTED_STATS=" - + " ENV_BRANCH=$BRANCH" - + " ENV_CHANGE=$CHANGE" - + " ENV_PATCHSET=$PATCHSET" - + " ENV_REFSPEC=$REFSPEC" - + " ENV_CHANGEURL=$CHANGE_URL" - + " Message\nwith newline"; + return cmdBuildStartedCommand; + } + + /** + * Set value. + * @param command value to set. + */ + public void setGerritCmdBuildStarted(String command) { + cmdBuildStartedCommand = command; } @Override