From b3a926151572e25ba03c8d6829d94a60a261a57c Mon Sep 17 00:00:00 2001 From: Steve Hill Date: Sat, 16 Feb 2019 03:06:16 -0800 Subject: [PATCH] Add additional logging for notification conditions (#516) - Extracted Condition interface that let's us log both if the condition was met and if the user preference allowed the notification to be sent - Conditions that have interesting logic are unit-tested - Moves to the message supplier style of logging, which is not invoked unless that level of logging is enabled - Introduces a build key format - [ProjectFullDisplayName #] to better indicate which build the messaging is coming from - Introduce SlackFactory in ActiveNotifier instead of passing in the dependencies to create a slack factory method - Introduce SlackNotificationsLogger - which writes to the system log with the build key embedded, and writes info-level messages to the build log with the plugin name embedded --- .../jenkins/plugins/slack/ActiveNotifier.java | 110 +++++++++--------- .../plugins/slack/JenkinsTokenExpander.java | 29 +++++ .../jenkins/plugins/slack/SlackNotifier.java | 37 +++--- .../plugins/slack/StandardSlackService.java | 3 +- .../jenkins/plugins/slack/TokenExpander.java | 7 ++ .../plugins/slack/decisions/Condition.java | 27 +++++ .../plugins/slack/decisions/Context.java | 36 ++++++ .../decisions/NotificationConditions.java | 33 ++++++ .../plugins/slack/decisions/OnAborted.java | 30 +++++ .../slack/decisions/OnBackToNormal.java | 32 +++++ .../plugins/slack/decisions/OnNotBuilt.java | 30 +++++ .../slack/decisions/OnRepeatedFailure.java | 31 +++++ .../slack/decisions/OnSingleFailure.java | 31 +++++ .../plugins/slack/decisions/OnSuccess.java | 30 +++++ .../plugins/slack/decisions/OnUnstable.java | 30 +++++ .../slack/logging/BuildAwareLogger.java | 6 + .../plugins/slack/logging/BuildKey.java | 19 +++ .../logging/SlackNotificationsLogger.java | 50 ++++++++ .../slack/MessageBuilderEscapeTest.java | 5 +- .../plugins/slack/decisions/ContextTest.java | 86 ++++++++++++++ .../decisions/NotificationConditionsTest.java | 23 ++++ .../slack/decisions/OnBackToNormalTest.java | 66 +++++++++++ .../decisions/OnRepeatedFailureTest.java | 55 +++++++++ .../slack/decisions/OnSingleFailureTest.java | 56 +++++++++ .../logging/SlackNotificationsLoggerTest.java | 55 +++++++++ .../slack/workflow/MessageBuilderTest.java | 12 +- 26 files changed, 853 insertions(+), 76 deletions(-) create mode 100644 src/main/java/jenkins/plugins/slack/JenkinsTokenExpander.java create mode 100644 src/main/java/jenkins/plugins/slack/TokenExpander.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/Condition.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/Context.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/NotificationConditions.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnAborted.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnBackToNormal.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnNotBuilt.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnRepeatedFailure.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnSingleFailure.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnSuccess.java create mode 100644 src/main/java/jenkins/plugins/slack/decisions/OnUnstable.java create mode 100644 src/main/java/jenkins/plugins/slack/logging/BuildAwareLogger.java create mode 100644 src/main/java/jenkins/plugins/slack/logging/BuildKey.java create mode 100644 src/main/java/jenkins/plugins/slack/logging/SlackNotificationsLogger.java create mode 100644 src/test/java/jenkins/plugins/slack/decisions/ContextTest.java create mode 100644 src/test/java/jenkins/plugins/slack/decisions/NotificationConditionsTest.java create mode 100644 src/test/java/jenkins/plugins/slack/decisions/OnBackToNormalTest.java create mode 100644 src/test/java/jenkins/plugins/slack/decisions/OnRepeatedFailureTest.java create mode 100644 src/test/java/jenkins/plugins/slack/decisions/OnSingleFailureTest.java create mode 100644 src/test/java/jenkins/plugins/slack/logging/SlackNotificationsLoggerTest.java diff --git a/src/main/java/jenkins/plugins/slack/ActiveNotifier.java b/src/main/java/jenkins/plugins/slack/ActiveNotifier.java index 72a495a7..4b39d6df 100755 --- a/src/main/java/jenkins/plugins/slack/ActiveNotifier.java +++ b/src/main/java/jenkins/plugins/slack/ActiveNotifier.java @@ -3,7 +3,6 @@ import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; -import hudson.model.BuildListener; import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.Result; @@ -15,57 +14,53 @@ import hudson.tasks.test.AbstractTestResultAction; import hudson.tasks.test.TestResult; import hudson.triggers.SCMTrigger; -import hudson.util.LogTaskListener; import jenkins.model.Jenkins; +import jenkins.plugins.slack.decisions.Context; +import jenkins.plugins.slack.decisions.NotificationConditions; +import jenkins.plugins.slack.logging.BuildAwareLogger; +import jenkins.plugins.slack.logging.BuildKey; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; -import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; -import org.jenkinsci.plugins.tokenmacro.TokenMacro; -import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; -import java.util.logging.Logger; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.SEVERE; - @SuppressWarnings("rawtypes") public class ActiveNotifier implements FineGrainedNotifier { - - private static final Logger logger = Logger.getLogger(SlackNotifier.class.getName()); - SlackNotifier notifier; - BuildListener listener; + private final Function, SlackService> slackFactory; + private final BuildAwareLogger log; + private final TokenExpander tokenExpander; - public ActiveNotifier(SlackNotifier notifier, BuildListener listener) { + public ActiveNotifier(SlackNotifier notifier, Function, SlackService> slackFactory, BuildAwareLogger log, TokenExpander tokenExpander) { super(); this.notifier = notifier; - this.listener = listener; - } - - private SlackService getSlack(AbstractBuild r) { - return notifier.newSlackService(r, listener); + this.slackFactory = slackFactory; + this.log = log; + this.tokenExpander = tokenExpander; } public void deleted(AbstractBuild r) { } public void started(AbstractBuild build) { + String key = BuildKey.format(build); CauseAction causeAction = build.getAction(CauseAction.class); if (causeAction != null) { Cause scmCause = causeAction.findCause(SCMTrigger.SCMTriggerCause.class); if (scmCause == null) { - MessageBuilder message = new MessageBuilder(notifier, build); + log.debug(key, "was not caused by SCM Trigger"); + MessageBuilder message = new MessageBuilder(notifier, build, log, tokenExpander); message.append(causeAction.getCauses().get(0).getShortDescription()); message.appendOpenLink(); if (notifier.getIncludeCustomMessage()) { @@ -75,6 +70,8 @@ public void started(AbstractBuild build) { // Cause was found, exit early to prevent double-message return; } + } else { + log.debug(key, "did not have a cause action"); } String changes = getChanges(build, notifier.getIncludeCustomMessage()); @@ -88,15 +85,16 @@ public void started(AbstractBuild build) { private void notifyStart(AbstractBuild build, String message) { AbstractProject project = build.getProject(); AbstractBuild lastBuild = project.getLastBuild(); + SlackService slack = slackFactory.apply(build); if (lastBuild != null) { AbstractBuild previousBuild = lastBuild.getPreviousCompletedBuild(); if (previousBuild == null) { - getSlack(build).publish(message, "good"); + slack.publish(message, "good"); } else { - getSlack(build).publish(message, getBuildColor(previousBuild)); + slack.publish(message, getBuildColor(previousBuild)); } } else { - getSlack(build).publish(message, "good"); + slack.publish(message, "good"); } } @@ -114,40 +112,34 @@ public void finalized(AbstractBuild r) { notifier.getIncludeFailedTests(), notifier.getIncludeCustomMessage()); if (notifier.getCommitInfoChoice().showAnything()) { message = message + "\n" + getCommitList(r); - } - getSlack(r).publish(message, getBuildColor(r)); + } + slackFactory.apply(r).publish(message, getBuildColor(r)); } } } public void completed(AbstractBuild r) { + String key = BuildKey.format(r); AbstractProject project = r.getProject(); - Result result = r.getResult(); AbstractBuild previousBuild = project.getLastBuild(); if (null != previousBuild) { do { previousBuild = previousBuild.getPreviousCompletedBuild(); } while (null != previousBuild && previousBuild.getResult() == Result.ABORTED); - Result previousResult = (null != previousBuild) ? previousBuild.getResult() : Result.SUCCESS; - if ((result == Result.ABORTED && notifier.getNotifyAborted()) - || (result == Result.FAILURE //notify only on single failed build - && previousResult != Result.FAILURE - && notifier.getNotifyFailure()) - || (result == Result.FAILURE //notify only on repeated failures - && previousResult == Result.FAILURE - && notifier.getNotifyRepeatedFailure()) - || (result == Result.NOT_BUILT && notifier.getNotifyNotBuilt()) - || (result == Result.SUCCESS - && (previousResult == Result.FAILURE || previousResult == Result.UNSTABLE) - && notifier.getNotifyBackToNormal()) - || (result == Result.SUCCESS && notifier.getNotifySuccess()) - || (result == Result.UNSTABLE && notifier.getNotifyUnstable())) { + if (null != previousBuild) { + log.info(key, "found #%d as previous completed, non-aborted build", previousBuild.getNumber()); + } else { + log.debug(key, "did not find previous completed, non-aborted build"); + } + + NotificationConditions conditions = NotificationConditions.create(notifier, log); + if (conditions.test(new Context(r, previousBuild))) { String message = getBuildStatusMessage(r, notifier.getIncludeTestSummary(), notifier.getIncludeFailedTests(), notifier.getIncludeCustomMessage()); if (notifier.getCommitInfoChoice().showAnything()) { message = message + "\n" + getCommitList(r); } - getSlack(r).publish(message, getBuildColor(r)); + slackFactory.apply(r).publish(message, getBuildColor(r)); } } } @@ -178,8 +170,9 @@ private Set getFailedTestIds(AbstractBuild currentBuild) { } String getChanges(AbstractBuild r, boolean includeCustomMessage) { + String key = BuildKey.format(r); if (!r.hasChangeSetComputed()) { - logger.info("No change set computed..."); + log.debug(key, "did not have change set computed"); return null; } ChangeLogSet changeSet = r.getChangeSet(); @@ -187,21 +180,21 @@ String getChanges(AbstractBuild r, boolean includeCustomMessage) { Set files = new HashSet<>(); for (Object o : changeSet.getItems()) { Entry entry = (Entry) o; - logger.info("Entry " + o); + log.debug(key, "adding changeset entry: %s", o); entries.add(entry); if (CollectionUtils.isNotEmpty(entry.getAffectedFiles())) { files.addAll(entry.getAffectedFiles()); } } if (entries.isEmpty()) { - logger.info("Empty change..."); + log.debug(key, "did not have entries in changeset"); return null; } Set authors = new HashSet<>(); for (Entry entry : entries) { authors.add(entry.getAuthor().getDisplayName()); } - MessageBuilder message = new MessageBuilder(notifier, r); + MessageBuilder message = new MessageBuilder(notifier, r, log, tokenExpander); message.append("Started by changes from "); message.append(StringUtils.join(authors, ", ")); message.append(" ("); @@ -215,15 +208,16 @@ String getChanges(AbstractBuild r, boolean includeCustomMessage) { } String getCommitList(AbstractBuild r) { + String buildKey = BuildKey.format(r); ChangeLogSet changeSet = r.getChangeSet(); List entries = new LinkedList<>(); for (Object o : changeSet.getItems()) { Entry entry = (Entry) o; - logger.info("Entry " + o); + log.debug(buildKey, "adding changeset entry: %s", o); entries.add(entry); } if (entries.isEmpty()) { - logger.info("Empty change..."); + log.debug(buildKey, "did not have entries in changeset"); Cause.UpstreamCause c = (Cause.UpstreamCause)r.getCause(Cause.UpstreamCause.class); if (c == null) { return "No Changes."; @@ -248,7 +242,7 @@ String getCommitList(AbstractBuild r) { } commits.add(commit.toString()); } - MessageBuilder message = new MessageBuilder(notifier, r); + MessageBuilder message = new MessageBuilder(notifier, r, log, tokenExpander); message.append("Changes:\n- "); message.append(StringUtils.join(commits, "\n- ")); return message.toString(); @@ -266,7 +260,7 @@ static String getBuildColor(AbstractBuild r) { } String getBuildStatusMessage(AbstractBuild r, boolean includeTestSummary, boolean includeFailedTests, boolean includeCustomMessage) { - MessageBuilder message = new MessageBuilder(notifier, r); + MessageBuilder message = new MessageBuilder(notifier, r, log, tokenExpander); message.appendStatusMessage(); message.appendDuration(); message.appendOpenLink(); @@ -298,12 +292,18 @@ public static class MessageBuilder { private StringBuilder message; private SlackNotifier notifier; + private final BuildAwareLogger log; + private final String buildKey; + private final TokenExpander tokenExpander; private AbstractBuild build; - public MessageBuilder(SlackNotifier notifier, AbstractBuild build) { + public MessageBuilder(SlackNotifier notifier, AbstractBuild build, BuildAwareLogger log, TokenExpander tokenExpander) { this.notifier = notifier; + this.log = log; + this.tokenExpander = tokenExpander; this.message = new StringBuilder(); this.build = build; + this.buildKey = BuildKey.format(build); startMessage(); } @@ -464,13 +464,9 @@ public MessageBuilder appendCustomMessage(Result buildResult) { if (customMessage == null || customMessage.isEmpty()) { customMessage = notifier.getCustomMessage(); } - try { - String replaced = TokenMacro.expandAll(build, new LogTaskListener(logger, INFO), customMessage, false, null); - message.append("\n"); - message.append(replaced); - } catch (MacroEvaluationException | IOException | InterruptedException e) { - logger.log(SEVERE, e.getMessage(), e); - } + String replaced = tokenExpander.expand(customMessage, build); + message.append("\n"); + message.append(replaced); return this; } diff --git a/src/main/java/jenkins/plugins/slack/JenkinsTokenExpander.java b/src/main/java/jenkins/plugins/slack/JenkinsTokenExpander.java new file mode 100644 index 00000000..a27982c7 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/JenkinsTokenExpander.java @@ -0,0 +1,29 @@ +package jenkins.plugins.slack; + +import hudson.model.AbstractBuild; +import hudson.model.TaskListener; +import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; +import org.jenkinsci.plugins.tokenmacro.TokenMacro; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JenkinsTokenExpander implements TokenExpander { + private static final Logger logger = Logger.getLogger(JenkinsTokenExpander.class.getName()); + private final TaskListener listener; + + public JenkinsTokenExpander(TaskListener listener) { + this.listener = listener; + } + + @Override + public String expand(String template, AbstractBuild build) { + try { + return TokenMacro.expandAll(build, listener, template, false, null); + } catch (MacroEvaluationException | IOException | InterruptedException e) { + logger.log(Level.SEVERE, "Failed to process custom message", e); + return "[UNPROCESSABLE] " + template; + } + } +} diff --git a/src/main/java/jenkins/plugins/slack/SlackNotifier.java b/src/main/java/jenkins/plugins/slack/SlackNotifier.java index f7fc2332..f39c7345 100755 --- a/src/main/java/jenkins/plugins/slack/SlackNotifier.java +++ b/src/main/java/jenkins/plugins/slack/SlackNotifier.java @@ -1,20 +1,15 @@ package jenkins.plugins.slack; -import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.domains.HostnameRequirement; import hudson.EnvVars; import hudson.Extension; -import hudson.ExtensionList; import hudson.Launcher; -import hudson.init.InitMilestone; -import hudson.init.Initializer; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Item; -import hudson.model.listeners.ItemListener; import hudson.security.ACL; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; @@ -24,6 +19,9 @@ import hudson.util.ListBoxModel; import jenkins.model.Jenkins; import jenkins.plugins.slack.config.GlobalCredentialMigrator; +import jenkins.plugins.slack.logging.BuildAwareLogger; +import jenkins.plugins.slack.logging.BuildKey; +import jenkins.plugins.slack.logging.SlackNotificationsLogger; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; @@ -34,8 +32,8 @@ import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.export.Exported; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; @@ -440,11 +438,14 @@ public boolean needsToRunAfterFinalized() { @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { - logger.info("Performing complete notifications"); - new ActiveNotifier(this, listener).completed(build); + String buildKey = BuildKey.format(build); + BuildAwareLogger log = createLogger(listener); + log.debug(buildKey, "Performing complete notifications"); + JenkinsTokenExpander tokenExpander = new JenkinsTokenExpander(listener); + new ActiveNotifier(this, slackFactory(listener), log, tokenExpander).completed(build); if (notifyRegression) { - logger.info("Performing finalize notifications"); - new ActiveNotifier(this, listener).finalized(build); + log.debug(buildKey, "Performing finalize notifications"); + new ActiveNotifier(this, slackFactory(listener), log, tokenExpander).finalized(build); } return true; } @@ -452,15 +453,25 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen @Override public boolean prebuild(AbstractBuild build, BuildListener listener) { if (startNotification) { - logger.info("Performing start notifications"); - new ActiveNotifier(this, listener).started(build); + BuildAwareLogger log = createLogger(listener); + log.debug(BuildKey.format(build), "Performing start notifications"); + new ActiveNotifier(this, slackFactory(listener), log, new JenkinsTokenExpander(listener)).started(build); } return super.prebuild(build, listener); } + private Function, SlackService> slackFactory(BuildListener listener) { + return b -> newSlackService(b, listener); + } + + private static BuildAwareLogger createLogger(BuildListener listener) { + return new SlackNotificationsLogger(logger, listener.getLogger()); + } + @Extension @Symbol("slackNotifier") public static class DescriptorImpl extends BuildStepDescriptor { + public static final String PLUGIN_DISPLAY_NAME = "Slack Notifications"; private String baseUrl; private String teamDomain; private String token; @@ -600,7 +611,7 @@ SlackService getSlackService(final String baseUrl, final String teamDomain, fina @Nonnull @Override public String getDisplayName() { - return "Slack Notifications"; + return PLUGIN_DISPLAY_NAME; } public FormValidation doTestConnection(@QueryParameter("baseUrl") final String baseUrl, diff --git a/src/main/java/jenkins/plugins/slack/StandardSlackService.java b/src/main/java/jenkins/plugins/slack/StandardSlackService.java index 6ce4aba5..0edd2eb1 100755 --- a/src/main/java/jenkins/plugins/slack/StandardSlackService.java +++ b/src/main/java/jenkins/plugins/slack/StandardSlackService.java @@ -5,6 +5,7 @@ import hudson.ProxyConfiguration; import hudson.security.ACL; import jenkins.model.Jenkins; +import jenkins.plugins.slack.logging.BuildKey; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; @@ -172,7 +173,7 @@ public boolean publish(String message, JSONArray attachments, String color) { logger.log(Level.WARNING, "Response Code: " + responseCode); result = false; } else { - logger.info("Posting succeeded"); + logger.fine("Posting succeeded"); } } catch (Exception e) { logger.log(Level.WARNING, "Error posting to Slack", e); diff --git a/src/main/java/jenkins/plugins/slack/TokenExpander.java b/src/main/java/jenkins/plugins/slack/TokenExpander.java new file mode 100644 index 00000000..4f2cfe76 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/TokenExpander.java @@ -0,0 +1,7 @@ +package jenkins.plugins.slack; + +import hudson.model.AbstractBuild; + +public interface TokenExpander { + String expand(String template, AbstractBuild build); +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/Condition.java b/src/main/java/jenkins/plugins/slack/decisions/Condition.java new file mode 100644 index 00000000..d9610d8d --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/Condition.java @@ -0,0 +1,27 @@ +package jenkins.plugins.slack.decisions; + +import jenkins.plugins.slack.logging.BuildAwareLogger; + +import java.util.function.Predicate; + +interface Condition extends Predicate { + boolean isMetBy(Context context); + boolean userPreferenceMatches(); + BuildAwareLogger log(); + + @Override + default boolean test(Context context) { + boolean isMet = isMetBy(context); + boolean preferences = userPreferenceMatches(); + if (isMet) { + if (preferences) { + log().info(context.currentKey(), "will send " + getClass().getSimpleName() + "Notification because build matches and user preferences allow it"); + } else { + log().debug(context.currentKey(), "will NOT send " + getClass().getSimpleName() + "Notification - build matches but user preferences do not allow it"); + } + } else { + log().debug(context.currentKey(), "does not match " + getClass().getSimpleName() + "Notification condition"); + } + return isMet && preferences; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/Context.java b/src/main/java/jenkins/plugins/slack/decisions/Context.java new file mode 100644 index 00000000..561d2f48 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/Context.java @@ -0,0 +1,36 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.AbstractBuild; +import hudson.model.Result; +import jenkins.plugins.slack.logging.BuildKey; + +import javax.annotation.Nullable; + +public class Context { + private final AbstractBuild current; + private final AbstractBuild previous; + + public Context(AbstractBuild current, AbstractBuild previous) { + this.current = current; + this.previous = previous; + } + + public String currentKey() { + return BuildKey.format(current); + } + + public Result previousResultOrSuccess() { + if (previous == null || previous.getResult() == null) { + return Result.SUCCESS; + } + return previous.getResult(); + } + + @Nullable + public Result currentResult() { + if (current == null) { + return null; + } + return current.getResult(); + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/NotificationConditions.java b/src/main/java/jenkins/plugins/slack/decisions/NotificationConditions.java new file mode 100644 index 00000000..e7274641 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/NotificationConditions.java @@ -0,0 +1,33 @@ +package jenkins.plugins.slack.decisions; + +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +public class NotificationConditions implements Predicate { + private final List> conditions; + + public NotificationConditions(List> conditions) { + this.conditions = conditions; + } + + public static NotificationConditions create(SlackNotifier preferences, BuildAwareLogger log) { + return new NotificationConditions(Arrays.asList( + new OnAborted(preferences, log), + new OnSingleFailure(preferences, log), + new OnRepeatedFailure(preferences, log), + new OnNotBuilt(preferences, log), + new OnBackToNormal(preferences, log), + new OnSuccess(preferences, log), + new OnUnstable(preferences, log) + )); + } + + @Override + public boolean test(Context context) { + return conditions.stream().anyMatch(p -> p.test(context)); + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnAborted.java b/src/main/java/jenkins/plugins/slack/decisions/OnAborted.java new file mode 100644 index 00000000..da782021 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnAborted.java @@ -0,0 +1,30 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnAborted implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnAborted(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.ABORTED; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyAborted(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnBackToNormal.java b/src/main/java/jenkins/plugins/slack/decisions/OnBackToNormal.java new file mode 100644 index 00000000..6b6b3341 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnBackToNormal.java @@ -0,0 +1,32 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnBackToNormal implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnBackToNormal(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + Result previousResult = context.previousResultOrSuccess(); + return context.currentResult() == Result.SUCCESS + && (previousResult == Result.FAILURE || previousResult == Result.UNSTABLE); + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyBackToNormal(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnNotBuilt.java b/src/main/java/jenkins/plugins/slack/decisions/OnNotBuilt.java new file mode 100644 index 00000000..573ab4ed --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnNotBuilt.java @@ -0,0 +1,30 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnNotBuilt implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnNotBuilt(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.NOT_BUILT; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyNotBuilt(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnRepeatedFailure.java b/src/main/java/jenkins/plugins/slack/decisions/OnRepeatedFailure.java new file mode 100644 index 00000000..f19b4f70 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnRepeatedFailure.java @@ -0,0 +1,31 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnRepeatedFailure implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnRepeatedFailure(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.FAILURE //notify only on repeated failures + && context.previousResultOrSuccess() == Result.FAILURE; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyRepeatedFailure(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnSingleFailure.java b/src/main/java/jenkins/plugins/slack/decisions/OnSingleFailure.java new file mode 100644 index 00000000..0b53e9d4 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnSingleFailure.java @@ -0,0 +1,31 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnSingleFailure implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnSingleFailure(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.FAILURE //notify only on single failed build + && context.previousResultOrSuccess() != Result.FAILURE; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyFailure(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnSuccess.java b/src/main/java/jenkins/plugins/slack/decisions/OnSuccess.java new file mode 100644 index 00000000..371d73f9 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnSuccess.java @@ -0,0 +1,30 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnSuccess implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnSuccess(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.SUCCESS; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifySuccess(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/decisions/OnUnstable.java b/src/main/java/jenkins/plugins/slack/decisions/OnUnstable.java new file mode 100644 index 00000000..a8925a24 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/decisions/OnUnstable.java @@ -0,0 +1,30 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.SlackNotifier; +import jenkins.plugins.slack.logging.BuildAwareLogger; + +public class OnUnstable implements Condition { + private final SlackNotifier preferences; + private final BuildAwareLogger log; + + public OnUnstable(SlackNotifier preferences, BuildAwareLogger log) { + this.preferences = preferences; + this.log = log; + } + + @Override + public boolean isMetBy(Context context) { + return context.currentResult() == Result.UNSTABLE; + } + + @Override + public boolean userPreferenceMatches() { + return preferences.getNotifyUnstable(); + } + + @Override + public BuildAwareLogger log() { + return log; + } +} diff --git a/src/main/java/jenkins/plugins/slack/logging/BuildAwareLogger.java b/src/main/java/jenkins/plugins/slack/logging/BuildAwareLogger.java new file mode 100644 index 00000000..22478455 --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/logging/BuildAwareLogger.java @@ -0,0 +1,6 @@ +package jenkins.plugins.slack.logging; + +public interface BuildAwareLogger { + void debug(String key, String message, Object... args); + void info(String key, String message, Object... args); +} diff --git a/src/main/java/jenkins/plugins/slack/logging/BuildKey.java b/src/main/java/jenkins/plugins/slack/logging/BuildKey.java new file mode 100644 index 00000000..92d426ee --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/logging/BuildKey.java @@ -0,0 +1,19 @@ +package jenkins.plugins.slack.logging; + +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; + +public class BuildKey { + private static final String UNKNOWN = "[UNKNOWN BUILD]"; + + public static String format(AbstractBuild build) { + if (build == null) { + return UNKNOWN; + } + AbstractProject project = build.getProject(); + if (project == null) { + return UNKNOWN; + } + return "[" + project.getFullDisplayName() + " #" + build.getNumber() + "]"; + } +} diff --git a/src/main/java/jenkins/plugins/slack/logging/SlackNotificationsLogger.java b/src/main/java/jenkins/plugins/slack/logging/SlackNotificationsLogger.java new file mode 100644 index 00000000..e305904a --- /dev/null +++ b/src/main/java/jenkins/plugins/slack/logging/SlackNotificationsLogger.java @@ -0,0 +1,50 @@ +package jenkins.plugins.slack.logging; + +import hudson.model.AbstractBuild; +import jenkins.plugins.slack.SlackNotifier; + +import java.io.PrintStream; +import java.util.logging.Logger; + +public class SlackNotificationsLogger implements BuildAwareLogger { + private static final String PLUGIN_KEY = String.format("[%s]", SlackNotifier.DescriptorImpl.PLUGIN_DISPLAY_NAME); + private final Logger system; + private final PrintStream user; + + public SlackNotificationsLogger(Logger system, PrintStream user) { + this.system = system; + this.user = user; + } + + /** + * Debug logs are only written to the system log. + * + * @see String#format(String, Object...) for message formatting options + * @see BuildKey#format(AbstractBuild) to create a build key easily + * + * @param key - Human-readable representation of the build + * @param message - message to be written to the system log + * @param args - arguments for the message + */ + @Override + public void debug(String key, String message, Object... args) { + system.fine(() -> String.join(" ", key, String.format(message, args))); + } + + /** + * Info logs are written to the system log with the build key and to the build's log without the key + * + * @see String#format(String, Object...) for message formatting options + * @see BuildKey#format(AbstractBuild) to create a build key easily + * + * @param key - Human-readable representation of the build + * @param message - message to be written to system log and build's console output + * @param args - arguments for the message + */ + @Override + public void info(String key, String message, Object... args) { + String formattedMessage = String.format(message, args); + system.info(() -> String.join(" ", key, formattedMessage)); + user.println(String.join(" ", PLUGIN_KEY, formattedMessage)); + } +} diff --git a/src/test/java/jenkins/plugins/slack/MessageBuilderEscapeTest.java b/src/test/java/jenkins/plugins/slack/MessageBuilderEscapeTest.java index 8e982edc..5c4b8f93 100644 --- a/src/test/java/jenkins/plugins/slack/MessageBuilderEscapeTest.java +++ b/src/test/java/jenkins/plugins/slack/MessageBuilderEscapeTest.java @@ -3,6 +3,7 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.ItemGroup; +import jenkins.plugins.slack.logging.BuildAwareLogger; import org.junit.BeforeClass; import org.junit.Test; @@ -23,6 +24,8 @@ public static void setupMessageBuilder() { AbstractProject job = mock(AbstractProject.class); ItemGroup group = mock(ItemGroup.class); SlackNotifier notifier = mock(SlackNotifier.class); + BuildAwareLogger logger = mock(BuildAwareLogger.class); + TokenExpander tokenExpander = mock(TokenExpander.class); when(build.getDisplayName()).thenReturn("build"); when(build.getProject()).thenReturn(project); @@ -34,7 +37,7 @@ public static void setupMessageBuilder() { when(job.getFullDisplayName()).thenReturn("job"); when(project.getFullDisplayName()).thenReturn("project"); - messageBuilder = new ActiveNotifier.MessageBuilder(notifier, build); + messageBuilder = new ActiveNotifier.MessageBuilder(notifier, build, logger, tokenExpander); } @Test diff --git a/src/test/java/jenkins/plugins/slack/decisions/ContextTest.java b/src/test/java/jenkins/plugins/slack/decisions/ContextTest.java new file mode 100644 index 00000000..20a7ed1e --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/decisions/ContextTest.java @@ -0,0 +1,86 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.AbstractBuild; +import hudson.model.Result; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.BDDMockito.given; + +public class ContextTest { + @Mock + private AbstractBuild previous; + @Mock + private AbstractBuild current; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldReturnSuccessIfPreviousBuildNull() { + Context context = new Context(current, null); + Result expected = Result.SUCCESS; + + Result actual = context.previousResultOrSuccess(); + + assertEquals(expected, actual); + } + + @Test + public void shouldReturnSuccessIfPreviousResultNull() { + given(previous.getResult()).willReturn(null); + Context context = new Context(current, previous); + Result expected = Result.SUCCESS; + + Result actual = context.previousResultOrSuccess(); + + assertEquals(expected, actual); + } + + @Test + public void shouldReturnPreviousResultIfPresent() { + Result expected = Result.UNSTABLE; + given(previous.getResult()).willReturn(expected); + Context context = new Context(current, previous); + + Result actual = context.previousResultOrSuccess(); + + assertEquals(expected, actual); + } + + @Test + public void shouldReturnCurrentResultIfPresent() { + Result expected = Result.NOT_BUILT; + given(current.getResult()).willReturn(expected); + Context context = new Context(current, previous); + + Result actual = context.currentResult(); + + assertEquals(expected, actual); + } + + @Test + public void shouldReturnNullIfCurrentBuildNull() { + Context context = new Context(null, previous); + + Result actual = context.currentResult(); + + assertNull(actual); + } + + @Test + public void shouldReturnNullIfCurrentResultNull() { + given(current.getResult()).willReturn(null); + Context context = new Context(current, previous); + + Result actual = context.currentResult(); + + assertNull(actual); + } +} diff --git a/src/test/java/jenkins/plugins/slack/decisions/NotificationConditionsTest.java b/src/test/java/jenkins/plugins/slack/decisions/NotificationConditionsTest.java new file mode 100644 index 00000000..19cd32ad --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/decisions/NotificationConditionsTest.java @@ -0,0 +1,23 @@ +package jenkins.plugins.slack.decisions; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertTrue; + +public class NotificationConditionsTest { + + @Test + public void shouldMatchIfAnyConditionMatches() { + NotificationConditions conditions = new NotificationConditions(Arrays.asList( + p -> false, + p -> false, + p -> true + )); + + boolean actual = conditions.test(null); + + assertTrue(actual); + } +} diff --git a/src/test/java/jenkins/plugins/slack/decisions/OnBackToNormalTest.java b/src/test/java/jenkins/plugins/slack/decisions/OnBackToNormalTest.java new file mode 100644 index 00000000..65884ae8 --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/decisions/OnBackToNormalTest.java @@ -0,0 +1,66 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.logging.BuildAwareLogger; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; + +public class OnBackToNormalTest { + @Mock + private Context context; + @Mock + private BuildAwareLogger log; + private OnBackToNormal subject = new OnBackToNormal(null, log); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldMeetConditionIfPreviousFailureIsNowSuccess() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.SUCCESS); + + boolean actual = subject.isMetBy(context); + + assertTrue(actual); + } + + @Test + public void shouldMeetConditionIfPreviousUnstableIsNowSuccess() { + given(context.previousResultOrSuccess()).willReturn(Result.UNSTABLE); + given(context.currentResult()).willReturn(Result.SUCCESS); + + boolean actual = subject.isMetBy(context); + + assertTrue(actual); + } + + @Test + public void shouldNotMeetConditionIfCurrentIsNotSuccess() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = subject.isMetBy(context); + + assertFalse(actual); + } + + @Test + public void shouldNotMeetConditionIfPreviousIsSuccess() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = subject.isMetBy(context); + + assertFalse(actual); + } + +} diff --git a/src/test/java/jenkins/plugins/slack/decisions/OnRepeatedFailureTest.java b/src/test/java/jenkins/plugins/slack/decisions/OnRepeatedFailureTest.java new file mode 100644 index 00000000..75c50d7d --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/decisions/OnRepeatedFailureTest.java @@ -0,0 +1,55 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.logging.BuildAwareLogger; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; + +public class OnRepeatedFailureTest { + @Mock + private Context context; + @Mock + private BuildAwareLogger log; + private OnRepeatedFailure condition = new OnRepeatedFailure(null, log); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldMeetConditionIfCurrentAndPreviousAreFailures() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = condition.isMetBy(context); + + assertTrue(actual); + } + + @Test + public void shouldNotMeetConditionIfCurrentIsUnstable() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.UNSTABLE); + + boolean actual = condition.isMetBy(context); + + assertFalse(actual); + } + + @Test + public void shouldNotMeetConditionIfPreviousIsUnstable() { + given(context.previousResultOrSuccess()).willReturn(Result.UNSTABLE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = condition.isMetBy(context); + + assertFalse(actual); + } +} diff --git a/src/test/java/jenkins/plugins/slack/decisions/OnSingleFailureTest.java b/src/test/java/jenkins/plugins/slack/decisions/OnSingleFailureTest.java new file mode 100644 index 00000000..c6d23b3c --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/decisions/OnSingleFailureTest.java @@ -0,0 +1,56 @@ +package jenkins.plugins.slack.decisions; + +import hudson.model.Result; +import jenkins.plugins.slack.logging.BuildAwareLogger; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; + +public class OnSingleFailureTest { + @Mock + private Context context; + @Mock + private BuildAwareLogger log; + private OnSingleFailure condition = new OnSingleFailure(null, log); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldMeetConditionIfCurrentIsFailureAndPreviousIsUnstable() { + given(context.previousResultOrSuccess()).willReturn(Result.UNSTABLE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = condition.isMetBy(context); + + assertTrue(actual); + } + + @Test + public void shouldNotMeetConditionIfCurrentIsFailureAndPreviousIsFailure() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.FAILURE); + + boolean actual = condition.isMetBy(context); + + assertFalse(actual); + } + + @Test + public void shouldNotMeetConditionIfCurrentIsSuccess() { + given(context.previousResultOrSuccess()).willReturn(Result.FAILURE); + given(context.currentResult()).willReturn(Result.SUCCESS); + + boolean actual = condition.isMetBy(context); + + assertFalse(actual); + } + +} \ No newline at end of file diff --git a/src/test/java/jenkins/plugins/slack/logging/SlackNotificationsLoggerTest.java b/src/test/java/jenkins/plugins/slack/logging/SlackNotificationsLoggerTest.java new file mode 100644 index 00000000..fb575015 --- /dev/null +++ b/src/test/java/jenkins/plugins/slack/logging/SlackNotificationsLoggerTest.java @@ -0,0 +1,55 @@ +package jenkins.plugins.slack.logging; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.PrintStream; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class SlackNotificationsLoggerTest { + @Mock + private Logger system; + @Mock + private PrintStream user; + @Captor + private ArgumentCaptor> messageSupplier; + private SlackNotificationsLogger logger; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + logger = new SlackNotificationsLogger(system, user); + } + + @Test + public void shouldOnlyWriteDebugMessagesToSystemLog() { + String expected = "[key] this message has number 15 within it"; + + logger.debug("[key]", "this message has number %d %s it", 15, "within"); + + verifyZeroInteractions(user); + verify(system).fine(messageSupplier.capture()); + assertEquals(expected, messageSupplier.getValue().get()); + } + + @Test + public void shouldWriteInfoMessagesToSystemAndUserLogs() { + String expectedUserLog = "[Slack Notifications] a 100% useful sort of message"; + String expectedSystemLog = "[Project #17] a 100% useful sort of message"; + + logger.info("[Project #17]", "a %s useful sort %s message", "100%", "of"); + + verify(user).println(expectedUserLog); + verify(system).info(messageSupplier.capture()); + assertEquals(expectedSystemLog, messageSupplier.getValue().get()); + } +} diff --git a/src/test/java/jenkins/plugins/slack/workflow/MessageBuilderTest.java b/src/test/java/jenkins/plugins/slack/workflow/MessageBuilderTest.java index 17e6d3b6..86196702 100644 --- a/src/test/java/jenkins/plugins/slack/workflow/MessageBuilderTest.java +++ b/src/test/java/jenkins/plugins/slack/workflow/MessageBuilderTest.java @@ -4,6 +4,8 @@ import hudson.model.FreeStyleProject; import hudson.model.ItemGroup; import jenkins.plugins.slack.ActiveNotifier; +import jenkins.plugins.slack.TokenExpander; +import jenkins.plugins.slack.logging.BuildAwareLogger; import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; @@ -14,6 +16,8 @@ import java.util.Arrays; import java.util.Collection; +import static org.mockito.Mockito.mock; + @RunWith(Parameterized.class) public class MessageBuilderTest extends TestCase { @@ -25,17 +29,17 @@ public class MessageBuilderTest extends TestCase { @Before @Override public void setUp() { - messageBuilder = new ActiveNotifier.MessageBuilder(null, build); + messageBuilder = new ActiveNotifier.MessageBuilder(null, build, mock(BuildAwareLogger.class), mock(TokenExpander.class)); } public MessageBuilderTest(String projectDisplayName, String buildDisplayName, String expectedResult) { - this.build = Mockito.mock(FreeStyleBuild.class); - FreeStyleProject project = Mockito.mock(FreeStyleProject.class); + this.build = mock(FreeStyleBuild.class); + FreeStyleProject project = mock(FreeStyleProject.class); Mockito.when(build.getProject()).thenReturn(project); Mockito.when(build.getDisplayName()).thenReturn(buildDisplayName); - ItemGroup ig = Mockito.mock(ItemGroup.class); + ItemGroup ig = mock(ItemGroup.class); Mockito.when(ig.getFullDisplayName()).thenReturn(""); Mockito.when(project.getParent()).thenReturn(ig); Mockito.when(project.getDisplayName()).thenReturn(projectDisplayName);