Repository: buildbot/buildbot

Issue:
reporters.gitlab: Use correct state
Fix mistake in code that uses incorrect state for gitlab.



Discussion:

Resolving patch:
diff --git a/master/buildbot/process/properties.py b/master/buildbot/process/properties.py
--- a/master/buildbot/process/properties.py
+++ b/master/buildbot/process/properties.py
@@ -185,6 +185,17 @@ class _PropertyMap(object):
     colon_minus_re = re.compile(r"(.*):-(.*)")
     colon_tilde_re = re.compile(r"(.*):~(.*)")
     colon_plus_re = re.compile(r"(.*):\+(.*)")
+    
+    colon_ternary_re = re.compile(r"""(?P<prop>.*) # the property to match
+                                      :            # colon
+                                      (?P<alt>\#)? # might have the alt marker '#'
+                                      \?           # question mark
+                                      (?P<delim>.) # the delimiter
+                                      (?P<true>.*) # sub-if-true
+                                      (?P=delim)   # the delimiter again
+                                      (?P<false>.*)# sub-if-false
+                                      """, re.VERBOSE)
+
     def __init__(self, properties):
         # use weakref here to avoid a reference loop
         self.properties = weakref.ref(properties)
@@ -225,10 +236,38 @@ def colon_plus(mo):
             else:
                 return ''
 
+        def colon_ternary(mo):
+            # %(prop:?:T:F)s
+            # if prop exists, use T; otherwise, F
+            # %(prop:#?:T:F)s
+            # if prop is true, use T; otherwise, F
+            groups = mo.groupdict()
+            
+            prop = groups['prop']
+            
+            if prop in self.temp_vals:
+                if groups['alt']:
+                    use_true = self.temp_vals[prop]
+                else:
+                    use_true = True
+            elif properties.has_key(prop):
+                if groups['alt']:
+                    use_true = properties[prop]
+                else:
+                    use_true = True
+            else:
+                use_true = False
+            
+            if use_true:
+                return groups['true']
+            else:
+                return groups['false']
+
         for regexp, fn in [
             ( self.colon_minus_re, colon_minus ),
             ( self.colon_tilde_re, colon_tilde ),
             ( self.colon_plus_re, colon_plus ),
+            ( self.colon_ternary_re, colon_ternary ),
             ]:
             mo = regexp.match(key)
             if mo:
@@ -436,6 +475,27 @@ def _parseColon_plus(self, d, kw, repl):
                defaultWhenFalse=False,
                elideNoneAs='')
 
+    colon_ternary_re = re.compile(r"""(?P<delim>.) # the delimiter
+                                      (?P<true>.*) # sub-if-true
+                                      (?P=delim)   # the delimiter again
+                                      (?P<false>.*)# sub-if-false
+                                      """, re.VERBOSE)
+
+    def _parseColon_ternary(self, d, kw, repl, defaultWhenFalse=False):
+        m = self.colon_ternary_re.match(repl)
+        if not m:
+            config.error("invalid Interpolate ternary expression for selector '%s' and delim '%s'" % (kw, repl[0]))
+            return None
+        m = m.groupdict()
+        return _Lookup(d, kw,
+               hasKey=Interpolate(m['true'], **self.kwargs),
+               default=Interpolate(m['false'], **self.kwargs),
+               defaultWhenFalse=defaultWhenFalse,
+               elideNoneAs='')
+
+    def _parseColon_ternary_hash(self, d, kw, repl):
+        return self._parseColon_ternary(d, kw, repl, defaultWhenFalse=True)
+
     def _parse(self, fmtstring):
         keys = _getInterpolationList(fmtstring)
         for key in keys:
@@ -443,13 +503,17 @@ def _parse(self, fmtstring):
                 d, kw, repl = self._parseSubstitution(key)
                 if repl is None:
                     repl = '-'
-                for char, fn in [
+                for pattern, fn in [
                     ( "-", self._parseColon_minus ),
                     ( "~", self._parseColon_tilde ),
                     ( "+", self._parseColon_plus ),
+                    ( "?", self._parseColon_ternary ),
+                    ( "#?", self._parseColon_ternary_hash )
                     ]:
-                    if repl[0] == char:
-                        self.interpolations[key] = fn(d, kw, repl[1:])
+                    junk, matches, tail = repl.partition(pattern)
+                    if not junk and matches:
+                        self.interpolations[key] = fn(d, kw, tail)
+                        break
                 if not self.interpolations.has_key(key):
                     config.error("invalid Interpolate default type '%s'" % repl[0])
 


Test patch:
diff --git a/master/buildbot/test/unit/test_process_properties.py b/master/buildbot/test/unit/test_process_properties.py
--- a/master/buildbot/test/unit/test_process_properties.py
+++ b/master/buildbot/test/unit/test_process_properties.py
@@ -197,6 +197,58 @@ def testColonPlusEmpty(self):
     def testColonPlusUnset(self):
         return self.doTestSimpleWithProperties('%(prop_nosuch:+present)s', '')
 
+
+    def testColonTernarySet(self):
+        return self.doTestSimpleWithProperties('%(prop_str:?:present:missing)s', 'present')
+
+    def testColonTernaryNone(self):
+        return self.doTestSimpleWithProperties('%(prop_none:?:present:missing)s', 'present')
+
+    def testColonTernaryZero(self):
+        return self.doTestSimpleWithProperties('%(prop_zero:?|present|missing)s', 'present')
+
+    def testColonTernaryOne(self):
+        return self.doTestSimpleWithProperties('%(prop_one:?:present:missing)s', 'present')
+
+    def testColonTernaryFalse(self):
+        return self.doTestSimpleWithProperties('%(prop_false:?|present|missing)s', 'present')
+
+    def testColonTernaryTrue(self):
+        return self.doTestSimpleWithProperties('%(prop_true:?:present:missing)s', 'present')
+
+    def testColonTernaryEmpty(self):
+        return self.doTestSimpleWithProperties('%(prop_empty:?ApresentAmissing)s', 'present')
+
+    def testColonTernaryUnset(self):
+        return self.doTestSimpleWithProperties('%(prop_nosuch:?#present#missing)s', 'missing')
+
+
+    def testColonTernaryHashSet(self):
+        return self.doTestSimpleWithProperties('%(prop_str:#?:truish:falsish)s', 'truish')
+        
+    def testColonTernaryHashNone(self):
+        # None is special-cased *differently* for '#?'
+        return self.doTestSimpleWithProperties('%(prop_none:#?|truish|falsish)s', 'falsish')
+
+    def testColonTernaryHashZero(self):
+        return self.doTestSimpleWithProperties('%(prop_zero:#?:truish:falsish)s', 'falsish')
+
+    def testColonTernaryHashOne(self):
+        return self.doTestSimpleWithProperties('%(prop_one:#?:truish:falsish)s', 'truish')
+
+    def testColonTernaryHashFalse(self):
+        return self.doTestSimpleWithProperties('%(prop_false:#?:truish:falsish)s', 'falsish')
+
+    def testColonTernaryHashTrue(self):
+        return self.doTestSimpleWithProperties('%(prop_true:#?|truish|falsish)s', 'truish')
+
+    def testColonTernaryHashEmpty(self):
+        return self.doTestSimpleWithProperties('%(prop_empty:#?:truish:falsish)s', 'falsish')
+
+    def testColonTernaryHashUnset(self):
+        return self.doTestSimpleWithProperties('%(prop_nosuch:#?.truish.falsish)s', 'falsish')
+
+
     def testClearTempValues(self):
         d = self.doTestSimpleWithProperties('', '',
                 prop_temp=lambda b: 'present')
@@ -274,6 +326,23 @@ def testTempValuePlusUnsetSet(self):
                 prop_nosuch=lambda b: 1)
 
 
+    def testTempValueColonTernaryTrue(self):
+        return self.doTestSimpleWithProperties('%(prop_temp:?:present:missing)s', 'present',
+                prop_temp=lambda b: True)
+
+    def testTempValueColonTernaryFalse(self):
+        return self.doTestSimpleWithProperties('%(prop_temp:?|present|missing)s', 'present',
+                prop_temp=lambda b: False)
+
+    def testTempValueColonTernaryHashTrue(self):
+        return self.doTestSimpleWithProperties('%(prop_temp:#?|truish|falsish)s', 'truish',
+                prop_temp=lambda b: 1)
+
+    def testTempValueColonTernaryHashFalse(self):
+        return self.doTestSimpleWithProperties('%(prop_temp:#?|truish|falsish)s', 'falsish',
+                prop_nosuch=lambda b: 0)
+
+
 class TestInterpolateConfigure(unittest.TestCase, ConfigErrorsMixin):
     """
     Test that Interpolate reports erros in the interpolation string
@@ -314,6 +383,14 @@ def test_nested_invalid_selector(self):
                 lambda: Interpolate("%(prop:some_prop:~%(garbage:test)s)s"))
 
 
+    def test_colon_ternary_bad_delimeter(self):
+        self.assertRaisesConfigError("invalid Interpolate ternary expression for selector 'P' and delim ':'",
+                lambda: Interpolate("echo '%(prop:P:?:one)s'"))
+
+    def test_colon_ternary_hash_bad_delimeter(self):
+        self.assertRaisesConfigError("invalid Interpolate ternary expression for selector 'P' and delim '|'",
+                lambda: Interpolate("echo '%(prop:P:#?|one)s'"))
+
 
 class TestInterpolatePositional(unittest.TestCase):
     def setUp(self):
@@ -417,6 +494,133 @@ def test_nested_property(self):
                             "echo 'so long!'")
         return d
 
+    def test_nested_property_deferred(self):
+        renderable = DeferredRenderable()
+        self.props.setProperty("missing", renderable, "test")
+        self.props.setProperty("project", "so long!", "test")
+        command = Interpolate("echo '%(prop:missing:~%(prop:project)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                            "echo 'so long!'")
+        renderable.callback(False)
+        return d
+
+    def test_property_substitute_recursively(self):
+        self.props.setProperty("project", "proj1", "test")
+        command = Interpolate("echo '%(prop:no_such:-%(prop:project)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj1'")
+        return d
+
+    def test_property_colon_ternary_present(self):
+        self.props.setProperty("project", "proj1", "test")
+        command = Interpolate("echo %(prop:project:?:defined:missing)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo defined")
+        return d
+
+    def test_property_colon_ternary_missing(self):
+        command = Interpolate("echo %(prop:project:?|defined|missing)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo missing")
+        return d
+
+    def test_property_colon_ternary_hash_true(self):
+        self.props.setProperty("project", "winbld", "test")
+        command = Interpolate("echo buildby-%(prop:project:#?:T:F)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo buildby-T")
+        return d
+
+    def test_property_colon_ternary_hash_false(self):
+        self.props.setProperty("project", "", "test")
+        command = Interpolate("echo buildby-%(prop:project:#?|T|F)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo buildby-F")
+        return d
+
+    def test_property_colon_ternary_substitute_recursively_true(self):
+        self.props.setProperty("P", "present", "test")
+        self.props.setProperty("one", "proj1", "test")
+        self.props.setProperty("two", "proj2", "test")
+        command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj1'")
+        return d
+
+    def test_property_colon_ternary_substitute_recursively_false(self):
+        self.props.setProperty("one", "proj1", "test")
+        self.props.setProperty("two", "proj2", "test")
+        command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj2'")
+        return d
+
+    def test_property_substitute_recursively(self):
+        self.props.setProperty("project", "proj1", "test")
+        command = Interpolate("echo '%(prop:no_such:-%(prop:project)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj1'")
+        return d
+
+    def test_property_colon_ternary_present(self):
+        self.props.setProperty("project", "proj1", "test")
+        command = Interpolate("echo %(prop:project:?:defined:missing)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo defined")
+        return d
+
+    def test_property_colon_ternary_missing(self):
+        command = Interpolate("echo %(prop:project:?|defined|missing)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo missing")
+        return d
+
+    def test_property_colon_ternary_hash_true(self):
+        self.props.setProperty("project", "winbld", "test")
+        command = Interpolate("echo buildby-%(prop:project:#?:T:F)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo buildby-T")
+        return d
+
+    def test_property_colon_ternary_hash_false(self):
+        self.props.setProperty("project", "", "test")
+        command = Interpolate("echo buildby-%(prop:project:#?|T|F)s")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo buildby-F")
+        return d
+
+    def test_property_colon_ternary_substitute_recursively_true(self):
+        self.props.setProperty("P", "present", "test")
+        self.props.setProperty("one", "proj1", "test")
+        self.props.setProperty("two", "proj2", "test")
+        command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj1'")
+        return d
+
+    def test_property_colon_ternary_substitute_recursively_false(self):
+        self.props.setProperty("one", "proj1", "test")
+        self.props.setProperty("two", "proj2", "test")
+        command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "echo 'proj2'")
+        return d
+
 class TestInterpolateSrc(unittest.TestCase):
     def setUp(self):
         self.props = Properties()
@@ -716,6 +920,15 @@ def testDictColonPlus(self):
                              "build-exists-.tar.gz")
         return d
 
+    def testDictColonTernary(self):
+        # test dict-style substitution with WithProperties
+        self.props.setProperty("prop1", "foo", "test")
+        command = WithProperties("build-%(prop1:?:exists:missing)s-%(prop2:?:exists:missing)s.tar.gz")
+        d = self.build.render(command)
+        d.addCallback(self.failUnlessEqual,
+                             "build-exists-missing.tar.gz")
+        return d
+
     def testEmpty(self):
         # None should render as ''
         self.props.setProperty("empty", None, "test")