Repository: buildbot/buildbot

Issue:
0.9.8: MailNotifier info gets "overwritten" if builders argument is the same
I have a function `mk_MailNotifiers` like this:

```
    red_builders = ["foobuilder"]
    green_builders = ["foobuilder"]
    mns = []
    red_modes = ["failing", "warnings", "exception", "cancelled"]
    if red_builders:
        mns.append(reporters.MailNotifier(
            relayhost='smtp.corp.imvu.com',
            fromaddr='buildbot-red@imvu.com',
            sendToInterestedUsers=False,
            extraRecipients=["larver@imvu.com"],
            lookup="imvu.com",
            builders=red_builders,
            mode=red_modes,
            messageFormatter=formatter))
    if green_builders:
        mns.append(reporters.MailNotifier(
            relayhost='smtp.corp.imvu.com',
            fromaddr='buildbot@imvu.com',
            sendToInterestedUsers=False,
            extraRecipients=["larver@imvu.com"],
            lookup="imvu.com",
            builders=green_builders,
            mode='passing',
            messageFormatter=formatter))
    return mns
```

and I assign it in my buildmaster config to the `c['services']` key.

If I run a build and cancel it, I do not get an email. If the build passes, then I do get an email.

If I reverse the order of the `if` branches, so that the green builder MailNotifier gets added first to the list, then I do get an email if I cancel a build.

So it looks like if the `builders` argument is the same for the two MailNotifiers, the one that comes last overwrites the earlier one(s).

I realize that in my case both `red_builders` and `green_builders` is the same, and that I should probably be using something more like `mode="all"` with just 1 MailNotifier, but I can't because I want red builds to have a different `fromaddr` argument (`buildbot-red@imvu.com`) than green builds.

Is this a legitimate bug or am I not using the MailNotifier correctly?


Discussion:
I tested on 0.9.9 also and the same behavior is present.
As a point of comparison, in Buildbot 0.8.5 (from which my code example above originates from), the behavior works as expected (no overwriting of settings).

Resolving patch:
diff --git a/master/buildbot/process/buildrequest.py b/master/buildbot/process/buildrequest.py
--- a/master/buildbot/process/buildrequest.py
+++ b/master/buildbot/process/buildrequest.py
@@ -62,7 +62,7 @@ def _getUnclaimedBrs(self, builderid):
 
     @defer.inlineCallbacks
     def collapse(self):
-        collapseBRs = []
+        brids = set()
 
         for brid in self.brids:
             # Get the BuildRequest object
@@ -86,10 +86,10 @@ def collapse(self):
 
                 canCollapse = yield collapseRequestsFn(self.master, bldr, br, unclaim_br)
                 if canCollapse is True:
-                    collapseBRs.append(unclaim_br)
+                    brids.add(unclaim_br['buildrequestid'])
 
-        brids = [b['buildrequestid'] for b in collapseBRs]
-        if collapseBRs:
+        brids = list(brids)
+        if brids:
             # Claim the buildrequests
             yield self.master.data.updates.claimBuildRequests(brids)
             # complete the buildrequest with result SKIPPED.
diff --git a/master/buildbot/reporters/notifier.py b/master/buildbot/reporters/notifier.py
--- a/master/buildbot/reporters/notifier.py
+++ b/master/buildbot/reporters/notifier.py
@@ -80,6 +80,7 @@ def checkConfig(self, mode=("failing", "passing", "warnings"),
                 self.name += "_schedulers_" + "+".join(schedulers)
             if branches is not None:
                 self.name += "_branches_" + "+".join(branches)
+            self.name += "_".join(mode)
 
         if '\n' in subject:
             config.error(


Test patch:
diff --git a/master/buildbot/test/unit/test_process_buildrequest.py b/master/buildbot/test/unit/test_process_buildrequest.py
--- a/master/buildbot/test/unit/test_process_buildrequest.py
+++ b/master/buildbot/test/unit/test_process_buildrequest.py
@@ -85,36 +85,36 @@ def collapseRequests_fn(master, builder, brdict1, brdict2):
         ]
         return self.do_request_collapse(rows, [19], [])
 
+    BASE_ROWS = [
+        fakedb.Builder(id=77, name='A'),
+        fakedb.SourceStamp(id=234, codebase='C'),
+        fakedb.Buildset(id=30, reason='foo',
+                        submitted_at=1300305712, results=-1),
+        fakedb.BuildsetSourceStamp(sourcestampid=234, buildsetid=30),
+        fakedb.SourceStamp(id=235, codebase='C'),
+        fakedb.Buildset(id=31, reason='foo',
+                        submitted_at=1300305712, results=-1),
+        fakedb.BuildsetSourceStamp(sourcestampid=235, buildsetid=31),
+        fakedb.SourceStamp(id=236, codebase='C'),
+        fakedb.Buildset(id=32, reason='foo',
+                        submitted_at=1300305712, results=-1),
+        fakedb.BuildsetSourceStamp(sourcestampid=236, buildsetid=32),
+        fakedb.BuildRequest(id=19, buildsetid=30, builderid=77,
+                            priority=13, submitted_at=1300305712, results=-1),
+        fakedb.BuildRequest(id=20, buildsetid=31, builderid=77,
+                            priority=13, submitted_at=1300305712, results=-1),
+        fakedb.BuildRequest(id=21, buildsetid=32, builderid=77,
+                            priority=13, submitted_at=1300305712, results=-1),
+    ]
+
     def test_collapseRequests_no_collapse(self):
 
         def collapseRequests_fn(master, builder, brdict1, brdict2):
             # Fail all collapse attempts
             return False
 
-        rows = [
-            fakedb.Builder(id=77, name='A'),
-            fakedb.SourceStamp(id=234, codebase='C'),
-            fakedb.Buildset(id=30, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=234, buildsetid=30),
-            fakedb.SourceStamp(id=235, codebase='C'),
-            fakedb.Buildset(id=31, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=235, buildsetid=31),
-            fakedb.SourceStamp(id=236, codebase='C'),
-            fakedb.Buildset(id=32, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=236, buildsetid=32),
-            fakedb.BuildRequest(id=19, buildsetid=30, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-            fakedb.BuildRequest(id=20, buildsetid=31, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-            fakedb.BuildRequest(id=21, buildsetid=32, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-        ]
-
         self.bldr.getCollapseRequestsFn = lambda: collapseRequests_fn
-        return self.do_request_collapse(rows, [21], [])
+        return self.do_request_collapse(self.BASE_ROWS, [21], [])
 
     def test_collapseRequests_collapse_all(self):
 
@@ -122,30 +122,17 @@ def collapseRequests_fn(master, builder, brdict1, brdict2):
             # collapse all attempts
             return True
 
-        rows = [
-            fakedb.Builder(id=77, name='A'),
-            fakedb.SourceStamp(id=234, codebase='C'),
-            fakedb.Buildset(id=30, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=234, buildsetid=30),
-            fakedb.SourceStamp(id=235, codebase='C'),
-            fakedb.Buildset(id=31, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=235, buildsetid=31),
-            fakedb.SourceStamp(id=236, codebase='C'),
-            fakedb.Buildset(id=32, reason='foo',
-                            submitted_at=1300305712, results=-1),
-            fakedb.BuildsetSourceStamp(sourcestampid=236, buildsetid=32),
-            fakedb.BuildRequest(id=19, buildsetid=30, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-            fakedb.BuildRequest(id=20, buildsetid=31, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-            fakedb.BuildRequest(id=21, buildsetid=32, builderid=77,
-                                priority=13, submitted_at=1300305712, results=-1),
-        ]
+        self.bldr.getCollapseRequestsFn = lambda: collapseRequests_fn
+        return self.do_request_collapse(self.BASE_ROWS, [21], [19, 20])
+
+    def test_collapseRequests_collapse_all_duplicates(self):
+
+        def collapseRequests_fn(master, builder, brdict1, brdict2):
+            # collapse all attempts
+            return True
 
         self.bldr.getCollapseRequestsFn = lambda: collapseRequests_fn
-        return self.do_request_collapse(rows, [21], [19, 20])
+        return self.do_request_collapse(self.BASE_ROWS, [21, 21], [19, 20])
 
     # As documented:
     # Sourcestamps are compatible if all of the below conditions are met: