Index: src/main/java/hudson/plugins/emailext/plugins/trigger/RegressionTrigger.java =================================================================== --- src/main/java/hudson/plugins/emailext/plugins/trigger/RegressionTrigger.java (revision 0) +++ src/main/java/hudson/plugins/emailext/plugins/trigger/RegressionTrigger.java (revision 0) @@ -0,0 +1,65 @@ +package hudson.plugins.emailext.plugins.trigger; + +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.Result; +import hudson.plugins.emailext.plugins.EmailTrigger; +import hudson.plugins.emailext.plugins.EmailTriggerDescriptor; + +public class RegressionTrigger extends EmailTrigger { + + public static final String TRIGGER_NAME = "Regression"; + + public RegressionTrigger() { + + } + + @Override + public <P extends AbstractProject<P,B>,B extends AbstractBuild<P,B>> + boolean trigger(B build) { + if (build.getPreviousBuild() == null) + return build.getResult() == Result.FAILURE; + if (build.getTestResultAction() == null) return false; + if (build.getPreviousBuild().getTestResultAction() == null) + return build.getTestResultAction().getFailCount() > 0; + return getNumFailures(build) > + getNumFailures(build.getPreviousBuild()); + } + + @Override + public EmailTriggerDescriptor getDescriptor() { + return DESCRIPTOR; + } + + public static DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + public static final class DescriptorImpl extends EmailTriggerDescriptor { + + @Override + public String getTriggerName() { + return TRIGGER_NAME; + } + + @Override + public EmailTrigger newInstance() { + return new RegressionTrigger(); + } + + @Override + public String getHelpText() { + return "An email will be sent any time there is a regression. A build is considered" + + "to regress whenever it has more failures than the previous build."; + } + + } + + @Override + public boolean getDefaultSendToDevs() { + return true; + } + + @Override + public boolean getDefaultSendToList() { + return true; + } +} Index: src/main/java/hudson/plugins/emailext/plugins/trigger/ImprovementTrigger.java =================================================================== --- src/main/java/hudson/plugins/emailext/plugins/trigger/ImprovementTrigger.java (revision 0) +++ src/main/java/hudson/plugins/emailext/plugins/trigger/ImprovementTrigger.java (revision 0) @@ -0,0 +1,69 @@ +package hudson.plugins.emailext.plugins.trigger; + +import java.util.logging.Logger; + +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.Result; +import hudson.plugins.emailext.plugins.EmailTrigger; +import hudson.plugins.emailext.plugins.EmailTriggerDescriptor; +import hudson.tasks.junit.CaseResult; + +public class ImprovementTrigger extends EmailTrigger { + + public static final String TRIGGER_NAME = "Improvement"; + + @Override + public <P extends AbstractProject<P,B>,B extends AbstractBuild<P,B>> + boolean trigger(B build) { + if (build.getPreviousBuild() == null) + return false; + if (build.getTestResultAction() == null) return false; + if (build.getPreviousBuild().getTestResultAction() == null) + return false; + + // The first part of the condition avoids accidental triggering for + // builds that aggregate downstream test results before those test + // results are available... + return build.getTestResultAction().getTotalCount() > 0 && + getNumFailures(build) < + getNumFailures(build.getPreviousBuild()); + } + + @Override + public EmailTriggerDescriptor getDescriptor() { + return DESCRIPTOR; + } + + public static DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + public static final class DescriptorImpl extends EmailTriggerDescriptor { + + @Override + public String getTriggerName() { + return TRIGGER_NAME; + } + + @Override + public EmailTrigger newInstance() { + return new ImprovementTrigger(); + } + + @Override + public String getHelpText() { + return "An email will be sent any time there is an improvement. A build is considered" + + "to have improved whenever it has fewer failures than the previous build."; + } + + } + + @Override + public boolean getDefaultSendToDevs() { + return true; + } + + @Override + public boolean getDefaultSendToList() { + return true; + } +} Index: src/main/java/hudson/plugins/emailext/plugins/content/FailedTestsContent.java =================================================================== --- src/main/java/hudson/plugins/emailext/plugins/content/FailedTestsContent.java (revision 16755) +++ src/main/java/hudson/plugins/emailext/plugins/content/FailedTestsContent.java (working copy) @@ -8,6 +8,7 @@ import hudson.tasks.junit.CaseResult; import hudson.tasks.test.AbstractTestResultAction; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -21,16 +22,25 @@ private static final String TOKEN = "FAILED_TESTS"; + private static final String SHOW_STACK_NAME = "showStack"; + private static final boolean SHOW_STACK_DEFAULT = true; + public String getToken() { return TOKEN; } public List<String> getArguments() { - return Collections.emptyList(); + return Arrays.asList(SHOW_STACK_NAME); } public String getHelpText() { - return "Displays failing unit test information, if any tests have failed."; + return "Displays failing unit test information, if any tests have failed." + + "<ul>\n" + + + "<li><i>" + SHOW_STACK_NAME + "</i> - indicates that " + + "most recent builds should be at the top.<br>\n" + + "Defaults to " + SHOW_STACK_DEFAULT + ".\n" + + "</ul>\n"; } public <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> @@ -46,6 +56,8 @@ int failCount = testResult.getFailCount(); + boolean showStacks = Args.get(args, SHOW_STACK_NAME, SHOW_STACK_DEFAULT); + if (failCount == 0){ buffer.append("All tests passed"); } else { @@ -55,22 +67,24 @@ List<CaseResult> failedTests = testResult.getFailedTests(); for (CaseResult failedTest: failedTests) { - outputTest(buffer, failedTest); + outputTest(buffer, failedTest, showStacks); } } return buffer.toString(); } - private void outputTest(StringBuffer buffer, CaseResult failedTest) { + private void outputTest(StringBuffer buffer, CaseResult failedTest, boolean showStack) { buffer.append(failedTest.getStatus().toString()); buffer.append(": "); buffer.append(failedTest.getClassName()); buffer.append("\n\n"); buffer.append("Error Message:\n"); buffer.append(failedTest.getErrorDetails()); - buffer.append("\n\nStack Trace:\n"); - buffer.append(failedTest.getErrorStackTrace()); + if (showStack) { + buffer.append("\n\nStack Trace:\n"); + buffer.append(failedTest.getErrorStackTrace()); + } buffer.append("\n\n"); } Index: src/main/java/hudson/plugins/emailext/plugins/content/ChangesSinceLastBuildContent.java =================================================================== --- src/main/java/hudson/plugins/emailext/plugins/content/ChangesSinceLastBuildContent.java (revision 16755) +++ src/main/java/hudson/plugins/emailext/plugins/content/ChangesSinceLastBuildContent.java (working copy) @@ -2,6 +2,8 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; +import hudson.model.Build; +import hudson.model.AbstractBuild.DependencyChange; import hudson.plugins.emailext.EmailType; import hudson.plugins.emailext.ExtendedEmailPublisher; import hudson.plugins.emailext.Util; @@ -15,6 +17,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; public class ChangesSinceLastBuildContent implements EmailContent { @@ -23,6 +26,9 @@ private static final String SHOW_PATHS_ARG_NAME = "showPaths"; private static final boolean SHOW_PATHS_DEFAULT_VALUE = false; + private static final String SHOW_DEPENDENCIES_NAME = "showDependencies"; + private static final boolean SHOW_DEPENDENCIES_VALUE = false; + private static final String FORMAT_ARG_NAME = "format"; private static final String FORMAT_DEFAULT_VALUE = "[%a] %m\\n"; private static final String FORMAT_DEFAULT_VALUE_WITH_PATHS = "[%a] %m%p\\n"; @@ -35,13 +41,17 @@ } public List<String> getArguments() { - return Arrays.asList(SHOW_PATHS_ARG_NAME, FORMAT_ARG_NAME, PATH_FORMAT_ARG_NAME); + return Arrays.asList(SHOW_PATHS_ARG_NAME, SHOW_DEPENDENCIES_NAME, FORMAT_ARG_NAME, PATH_FORMAT_ARG_NAME); } public String getHelpText() { return "Displays the changes since the last build.\n" + "<ul>\n" + + "<li><i>" + SHOW_DEPENDENCIES_NAME + "</i> - if true, changes to " + + "projects this build depends on are shown.<br>\n" + + "Defaults to " + SHOW_DEPENDENCIES_VALUE + ".\n" + + "<li><i>" + SHOW_PATHS_ARG_NAME + "</i> - if true, the paths " + "modified by a commit are shown.<br>\n" + "Defaults to " + SHOW_PATHS_DEFAULT_VALUE + ".\n" + @@ -72,6 +82,18 @@ for (ChangeLogSet.Entry entry : build.getChangeSet()) { appendEntry(buf, entry, formatString, pathFormatString); } + boolean showDependencies = Args.get(args, SHOW_DEPENDENCIES_NAME, SHOW_DEPENDENCIES_VALUE); + if (showDependencies && build.getPreviousBuild() != null) + for (Entry<AbstractProject, DependencyChange> e : + build.getDependencyChanges(build.getPreviousBuild()).entrySet()) { + buf.append("\n=======================\n"); + buf.append("\nChanges in ").append(e.getKey().getName()).append(":\n"); + for (AbstractBuild<P, B> b : e.getValue().getBuilds()) { + for (ChangeLogSet.Entry entry : b.getChangeSet()) { + appendEntry(buf, entry, formatString, pathFormatString); + } + } + } return buf.toString(); } Index: src/main/java/hudson/plugins/emailext/plugins/EmailTrigger.java =================================================================== --- src/main/java/hudson/plugins/emailext/plugins/EmailTrigger.java (revision 16755) +++ src/main/java/hudson/plugins/emailext/plugins/EmailTrigger.java (working copy) @@ -4,6 +4,10 @@ import hudson.model.AbstractProject; import hudson.plugins.emailext.EmailType; import hudson.plugins.emailext.ExtendedEmailPublisher; +import hudson.tasks.junit.TestResult; +import hudson.tasks.test.AbstractTestResultAction; +import hudson.tasks.test.AggregatedTestResultAction; +import hudson.tasks.test.AggregatedTestResultAction.ChildReport; public abstract class EmailTrigger { @@ -44,5 +48,31 @@ public boolean getDefaultSendToDevs() { return false; } + + /** + * Determine the number of direct failures in the given build. If it aggregates + * downstream results, ignore contributed failures. This is because at the time + * this trigger runs, the current build's aggregated results aren't available + * yet, but those of the previous build may be. + */ + protected <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> int getNumFailures( + B build) { + AbstractTestResultAction a = build.getTestResultAction(); + if (a instanceof AggregatedTestResultAction) { + int result = 0; + AggregatedTestResultAction action = + (AggregatedTestResultAction) a; + for (ChildReport cr : action.getChildReports()) { + if (cr.child.getParent().equals(build.getParent())) { + if (cr.result instanceof TestResult) { + TestResult tr = (TestResult) cr.result; + result += tr.getFailCount(); + } + } + } + return result; + } + return a.getFailCount(); + } } Index: src/main/java/hudson/plugins/emailext/EmailExtensionPlugin.java =================================================================== --- src/main/java/hudson/plugins/emailext/EmailExtensionPlugin.java (revision 16755) +++ src/main/java/hudson/plugins/emailext/EmailExtensionPlugin.java (working copy) @@ -17,6 +17,8 @@ import hudson.plugins.emailext.plugins.content.ProjectURLContent; import hudson.plugins.emailext.plugins.trigger.FailureTrigger; import hudson.plugins.emailext.plugins.trigger.FixedTrigger; +import hudson.plugins.emailext.plugins.trigger.ImprovementTrigger; +import hudson.plugins.emailext.plugins.trigger.RegressionTrigger; import hudson.plugins.emailext.plugins.trigger.StillFailingTrigger; import hudson.plugins.emailext.plugins.trigger.StillUnstableTrigger; import hudson.plugins.emailext.plugins.trigger.SuccessTrigger; @@ -61,6 +63,8 @@ addEmailTriggerPlugin(StillUnstableTrigger.DESCRIPTOR); addEmailTriggerPlugin(SuccessTrigger.DESCRIPTOR); addEmailTriggerPlugin(FixedTrigger.DESCRIPTOR); + addEmailTriggerPlugin(ImprovementTrigger.DESCRIPTOR); + addEmailTriggerPlugin(RegressionTrigger.DESCRIPTOR); } private void addEmailContentPlugin(EmailContent content) {