You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by rh...@apache.org on 2009/11/13 16:33:05 UTC

svn commit: r835879 - in /qpid/trunk/qpid: cpp/src/tests/cluster_tests.py java/testkit/testkit.py python/qpid/address.py python/qpid/brokertest.py python/qpid/tests/address.py python/qpid/tests/messaging.py

Author: rhs
Date: Fri Nov 13 15:33:04 2009
New Revision: 835879

URL: http://svn.apache.org/viewvc?rev=835879&view=rev
Log:
changed address syntax to permit more complex subjects, added escaping, improved error reporting

Modified:
    qpid/trunk/qpid/cpp/src/tests/cluster_tests.py
    qpid/trunk/qpid/java/testkit/testkit.py
    qpid/trunk/qpid/python/qpid/address.py
    qpid/trunk/qpid/python/qpid/brokertest.py
    qpid/trunk/qpid/python/qpid/tests/address.py
    qpid/trunk/qpid/python/qpid/tests/messaging.py

Modified: qpid/trunk/qpid/cpp/src/tests/cluster_tests.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/tests/cluster_tests.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/cpp/src/tests/cluster_tests.py (original)
+++ qpid/trunk/qpid/cpp/src/tests/cluster_tests.py Fri Nov 13 15:33:04 2009
@@ -34,8 +34,8 @@
         # Start a cluster, send some messages to member 0.
         cluster = self.cluster(2)
         s0 = cluster[0].connect().session()
-        s0.sender("q {create:always}").send(messaging.Message("x"))
-        s0.sender("q {create:always}").send(messaging.Message("y"))
+        s0.sender("q; {create:always}").send(messaging.Message("x"))
+        s0.sender("q; {create:always}").send(messaging.Message("y"))
         s0.connection.close()
 
         # Verify messages available on member 1.

Modified: qpid/trunk/qpid/java/testkit/testkit.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/testkit/testkit.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/java/testkit/testkit.py (original)
+++ qpid/trunk/qpid/java/testkit/testkit.py Fri Nov 13 15:33:04 2009
@@ -42,7 +42,7 @@
     # temp hack: just creating the queue here and closing it.
     def start_error_watcher(self,broker=None):
         ssn = broker.connect().session()
-        err_watcher = ssn.receiver("control {create:always}", capacity=1)
+        err_watcher = ssn.receiver("control; {create:always}", capacity=1)
         ssn.close()  
 
     def client(self,**options):
@@ -76,7 +76,7 @@
     # temp hack: just creating a receiver and closing session soon after.
     def monitor_clients(self,broker=None,run_time=600,error_ck_freq=60):
         ssn = broker.connect().session()
-        err_watcher = ssn.receiver("control {create:always}", capacity=1)
+        err_watcher = ssn.receiver("control; {create:always}", capacity=1)
         i = run_time/error_ck_freq
         for j in range(i):            
             try:   

Modified: qpid/trunk/qpid/python/qpid/address.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/address.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/address.py (original)
+++ qpid/trunk/qpid/python/qpid/address.py Fri Nov 13 15:33:04 2009
@@ -34,22 +34,33 @@
 LBRACE = Type("LBRACE", r"\{")
 RBRACE = Type("RBRACE", r"\}")
 COLON = Type("COLON", r":")
-COMMA = Type("COMMA", r",")
+SEMI = Type("SEMI", r";")
 SLASH = Type("SLASH", r"/")
-ID = Type("ID", r'[a-zA-Z_][a-zA-Z0-9_.#*-]*')
+COMMA = Type("COMMA", r",")
 NUMBER = Type("NUMBER", r'[+-]?[0-9]*\.?[0-9]+')
+ID = Type("ID", r'[a-zA-Z_][a-zA-Z0-9_]*')
 STRING = Type("STRING", r""""(?:[^\\"]|\\.)*"|'(?:[^\\']|\\.)*'""")
+ESC = Type("ESC", r"\\[^ux]|\\x[0-9][0-9]|\\u[0-9][0-9][0-9][0-0]")
+SYM = Type("SYM", r"[.#*%@$^!+-]")
 WSPACE = Type("WSPACE", r"[ \n\r\t]+")
 EOF = Type("EOF")
 
 class Token:
 
-  def __init__(self, type, value):
+  def __init__(self, type, value, input, position):
     self.type = type
     self.value = value
+    self.input = input
+    self.position = position
+
+  def line_info(self):
+    return line_info(self.input, self.position)
 
   def __repr__(self):
-    return "%s: %r" % (self.type, self.value)
+    if self.value is None:
+      return repr(self.type)
+    else:
+      return "%s(%r)" % (self.type, self.value)
 
 joined = "|".join(["(%s)" % t.pattern for t in TYPES])
 LEXER = re.compile(joined)
@@ -83,15 +94,51 @@
     m = LEXER.match(st, pos)
     if m is None:
       line, ln, col = line_info(st, pos)
-      raise LexError("unrecognized character in <string>:%s,%s: %s" % (ln, col, line))
+      raise LexError("unrecognized characters line:%s,%s: %s" % (ln, col, line))
     else:
       idx = m.lastindex
-      t = Token(TYPES[idx - 1], m.group(idx))
+      t = Token(TYPES[idx - 1], m.group(idx), st, pos)
       yield t
     pos = m.end()
-  yield Token(EOF, None)
+  yield Token(EOF, None, st, pos)
 
-class ParseError(Exception): pass
+def tok2str(tok):
+  if tok.type is STRING:
+    return eval(tok.value)
+  elif tok.type is ESC:
+    if tok.value[1] in ("x", "u"):
+      return eval('"%s"' % tok.value)
+    else:
+      return tok.value[1]
+  else:
+    return tok.value
+
+def tok2obj(tok):
+  if tok.type in (STRING, NUMBER):
+    return eval(tok.value)
+  else:
+    return tok.value
+
+def toks2str(toks):
+  if toks:
+    return "".join(map(tok2str, toks))
+  else:
+    return None
+
+class ParseError(Exception):
+
+  def __init__(self, token, *expected):
+    line, ln, col = token.line_info()
+    exp = ", ".join(map(str, expected))
+    if len(expected) > 1:
+      exp = "(%s)" % exp
+    if expected:
+      msg = "expecting %s, got %s line:%s,%s:%s" % (exp, token, ln, col, line)
+    else:
+      msg = "unexpected token %s line:%s,%s:%s" % (token, ln, col, line)
+    Exception.__init__(self, msg)
+    self.token = token
+    self.expected = expected
 
 class Parser:
 
@@ -107,46 +154,62 @@
 
   def eat(self, *types):
     if types and not self.matches(*types):
-      raise ParseError("expecting %s -- got %s" % (", ".join(map(str, types)), self.next()))
+      raise ParseError(self.next(), *types)
     else:
       t = self.next()
       self.idx += 1
       return t
 
+  def eat_until(self, *types):
+    result = []
+    while not self.matches(*types):
+      result.append(self.eat())
+    return result
+
   def parse(self):
     result = self.address()
     self.eat(EOF)
     return result
 
   def address(self):
-    name = self.eat(ID).value
-    subject = None
-    options = None
+    name = toks2str(self.eat_until(SLASH, SEMI, EOF))
+
+    if name is None:
+      raise ParseError(self.next())
+
     if self.matches(SLASH):
       self.eat(SLASH)
-      if self.matches(ID):
-        subject = self.eat(ID).value
-      else:
-        subject = ""
-    elif self.matches(LBRACE):
+      subject = toks2str(self.eat_until(SEMI, EOF))
+    else:
+      subject = None
+
+    if self.matches(SEMI):
+      self.eat(SEMI)
       options = self.map()
+    else:
+      options = None
     return name, subject, options
 
   def map(self):
     self.eat(LBRACE)
+
     result = {}
     while True:
-      if self.matches(RBRACE):
-        self.eat(RBRACE)
-        break
-      else:
-        if self.matches(ID):
-          n, v = self.nameval()
-          result[n] = v
-        elif self.matches(COMMA):
+      if self.matches(ID):
+        n, v = self.nameval()
+        result[n] = v
+        if self.matches(COMMA):
           self.eat(COMMA)
+        elif self.matches(RBRACE):
+          break
         else:
-          raise ParseError("expecting (ID, COMMA), got %s" % self.next())
+          raise ParseError(self.next(), COMMA, RBRACE)
+      elif self.matches(RBRACE):
+        break
+      else:
+        raise ParseError(self.next(), ID, RBRACE)
+
+    self.eat(RBRACE)
     return result
 
   def nameval(self):
@@ -156,16 +219,14 @@
     return (name, val)
 
   def value(self):
-    if self.matches(NUMBER, STRING):
-      return eval(self.eat().value)
-    elif self.matches(ID):
-      return self.eat().value
+    if self.matches(NUMBER, STRING, ID):
+      return tok2obj(self.eat())
     elif self.matches(LBRACE):
       return self.map()
     else:
-      raise ParseError("expecting (NUMBER, STRING, LBRACE) got %s" % self.next())
+      raise ParseError(self.next(), NUMBER, STRING, ID, LBRACE)
 
 def parse(addr):
   return Parser(lex(addr)).parse()
 
-__all__ = ["parse"]
+__all__ = ["parse", "ParseError"]

Modified: qpid/trunk/qpid/python/qpid/brokertest.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/brokertest.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/brokertest.py (original)
+++ qpid/trunk/qpid/python/qpid/brokertest.py Fri Nov 13 15:33:04 2009
@@ -176,25 +176,25 @@
 
     def send_message(self, queue, message):
         s = self.connect().session()
-        s.sender(queue+" {create:always}").send(message)
+        s.sender(queue+"; {create:always}").send(message)
         s.connection.close()
 
     def send_messages(self, queue, messages):
         s = self.connect().session()
-        sender = s.sender(queue+" {create:always}")
+        sender = s.sender(queue+"; {create:always}")
         for m in messages: sender.send(m)
         s.connection.close()
 
     def get_message(self, queue):
         s = self.connect().session()
-        m = s.receiver(queue+" {create:always}", capacity=1).fetch(timeout=1)
+        m = s.receiver(queue+"; {create:always}", capacity=1).fetch(timeout=1)
         s.acknowledge()
         s.connection.close()
         return m
 
     def get_messages(self, queue, n):
         s = self.connect().session()
-        receiver = s.receiver(queue+" {create:always}", capacity=n)
+        receiver = s.receiver(queue+"; {create:always}", capacity=n)
         m = [receiver.fetch(timeout=1) for i in range(n)]
         s.acknowledge()
         s.connection.close()

Modified: qpid/trunk/qpid/python/qpid/tests/address.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/tests/address.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/tests/address.py (original)
+++ qpid/trunk/qpid/python/qpid/tests/address.py Fri Nov 13 15:33:04 2009
@@ -18,18 +18,101 @@
 #
 
 from qpid.tests import Test
-from qpid.address import parse
+from qpid.address import parse, ParseError
 
 class AddressTests(Test):
 
+  def valid(self, addr, name=None, subject=None, options=None):
+    expected = (name, subject, options)
+    got = parse(addr)
+    assert expected == got, "expected %s, got %s" % (expected, got)
+
+  def invalid(self, addr, error=None):
+    try:
+      p = parse(addr)
+      assert False, "invalid address parsed: %s" % p
+    except ParseError, e:
+      assert error == str(e), "expected %r, got %r" % (error, str(e))
+
   def testHash(self):
-    name, subject, options = parse("foo/bar.#")
-    assert name == "foo", name
-    assert subject == "bar.#", bar
-    assert options == None
+    self.valid("foo/bar.#", "foo", "bar.#")
 
   def testStar(self):
-    name, subject, options = parse("foo/bar.*")
-    assert name == "foo", name
-    assert subject == "bar.*", bar
-    assert options == None
+    self.valid("foo/bar.*", "foo", "bar.*")
+
+  def testColon(self):
+    self.valid("foo.bar/baz.qux:moo:arf", "foo.bar", "baz.qux:moo:arf")
+
+  def testOptions(self):
+    self.valid("foo.bar/baz.qux:moo:arf; {key: value}",
+               "foo.bar", "baz.qux:moo:arf", {"key": "value"})
+
+  def testOptionsTrailingComma(self):
+    self.valid("name/subject; {key: value,}", "name", "subject", {"key": "value"})
+
+  def testSemiSubject(self):
+    self.valid("foo.bar/'baz.qux;moo:arf'; {key: value}",
+               "foo.bar", "baz.qux;moo:arf", {"key": "value"})
+
+  def testCommaSubject(self):
+    self.valid("foo.bar/baz.qux.{moo,arf}", "foo.bar", "baz.qux.{moo,arf}")
+
+  def testCommaSubjectOptions(self):
+    self.valid("foo.bar/baz.qux.{moo,arf}; {key: value}", "foo.bar",
+               "baz.qux.{moo,arf}", {"key": "value"})
+
+  def testUnbalanced(self):
+    self.valid("foo.bar/baz.qux.{moo,arf; {key: value}", "foo.bar",
+               "baz.qux.{moo,arf", {"key": "value"})
+
+  def testSlashQuote(self):
+    self.valid("foo.bar\\/baz.qux.{moo,arf; {key: value}", "foo.bar/baz.qux.{moo,arf",
+               None, {"key": "value"})
+
+  def testSlashEsc(self):
+    self.valid("foo.bar\\x00baz.qux.{moo,arf; {key: value}", "foo.bar\x00baz.qux.{moo,arf",
+               None, {"key": "value"})
+
+  def testNoName(self):
+    self.invalid("; {key: value}", "unexpected token SEMI(';') line:1,0:; {key: value}")
+
+  def testEmpty(self):
+    self.invalid("", "unexpected token EOF line:1,0:")
+
+  def testNoNameSlash(self):
+    self.invalid("/asdf; {key: value}",
+                 "unexpected token SLASH('/') line:1,0:/asdf; {key: value}")
+
+  def testBadOptions1(self):
+    self.invalid("name/subject; {",
+                 "expecting (ID, RBRACE), got EOF line:1,15:name/subject; {")
+
+  def testBadOptions2(self):
+    self.invalid("name/subject; { 3",
+                 "expecting (ID, RBRACE), got NUMBER('3') "
+                 "line:1,16:name/subject; { 3")
+
+  def testBadOptions3(self):
+    self.invalid("name/subject; { key:",
+                 "expecting (NUMBER, STRING, ID, LBRACE), got EOF "
+                 "line:1,20:name/subject; { key:")
+
+  def testBadOptions4(self):
+    self.invalid("name/subject; { key: value",
+                 "expecting (COMMA, RBRACE), got EOF "
+                 "line:1,26:name/subject; { key: value")
+
+  def testBadOptions5(self):
+    self.invalid("name/subject; { key: value asdf",
+                 "expecting (COMMA, RBRACE), got ID('asdf') "
+                 "line:1,27:name/subject; { key: value asdf")
+
+  def testBadOptions6(self):
+    self.invalid("name/subject; { key: value,",
+                 "expecting (ID, RBRACE), got EOF "
+                 "line:1,27:name/subject; { key: value,")
+
+  def testBadOptions7(self):
+    self.invalid("name/subject; { key: value } asdf",
+                 "expecting EOF, got ID('asdf') "
+                 "line:1,29:name/subject; { key: value } asdf")

Modified: qpid/trunk/qpid/python/qpid/tests/messaging.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/tests/messaging.py?rev=835879&r1=835878&r2=835879&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/tests/messaging.py (original)
+++ qpid/trunk/qpid/python/qpid/tests/messaging.py Fri Nov 13 15:33:04 2009
@@ -66,7 +66,7 @@
       return "%s[%s, %s]" % (base, count, self.test_id)
 
   def ping(self, ssn):
-    PING_Q = 'ping-queue {create: always}'
+    PING_Q = 'ping-queue; {create: always}'
     # send a message
     sender = ssn.sender(PING_Q, durable=self.durable())
     content = self.content("ping")
@@ -190,7 +190,7 @@
     self.conn.close()
     assert not self.conn.connected()
 
-ACK_Q = 'test-ack-queue {create: always}'
+ACK_Q = 'test-ack-queue; {create: always}'
 
 class SessionTests(Base):
 
@@ -202,7 +202,7 @@
     return self.conn.session()
 
   def testSender(self):
-    snd = self.ssn.sender('test-snd-queue {create: always}',
+    snd = self.ssn.sender('test-snd-queue; {create: always}',
                           durable=self.durable())
     snd2 = self.ssn.sender(snd.target, durable=self.durable())
     assert snd is not snd2
@@ -216,7 +216,7 @@
     self.ssn.acknowledge(msg)
 
   def testReceiver(self):
-    rcv = self.ssn.receiver('test-rcv-queue {create: always}')
+    rcv = self.ssn.receiver('test-rcv-queue; {create: always}')
     rcv2 = self.ssn.receiver(rcv.source)
     assert rcv is not rcv2
     rcv2.close()
@@ -229,7 +229,7 @@
     self.ssn.acknowledge(msg)
 
   def testNextReceiver(self):
-    ADDR = 'test-next-rcv-queue {create: always}'
+    ADDR = 'test-next-rcv-queue; {create: always}'
     rcv1 = self.ssn.receiver(ADDR, capacity=UNLIMITED)
     rcv2 = self.ssn.receiver(ADDR, capacity=UNLIMITED)
     rcv3 = self.ssn.receiver(ADDR, capacity=UNLIMITED)
@@ -258,7 +258,7 @@
     self.ssn.acknowledge()
 
   def testStart(self):
-    START_Q = 'test-start-queue {create: always}'
+    START_Q = 'test-start-queue; {create: always}'
     rcv = self.ssn.receiver(START_Q)
     assert not rcv.started
     self.ssn.start()
@@ -267,7 +267,7 @@
     assert rcv.started
 
   def testStop(self):
-    STOP_Q = 'test-stop-queue {create: always}'
+    STOP_Q = 'test-stop-queue; {create: always}'
     self.ssn.start()
     rcv = self.ssn.receiver(STOP_Q)
     assert rcv.started
@@ -345,8 +345,8 @@
     return contents
 
   def txTest(self, commit):
-    TX_Q = 'test-tx-queue {create: always}'
-    TX_Q_COPY = 'test-tx-queue-copy {create: always}'
+    TX_Q = 'test-tx-queue; {create: always}'
+    TX_Q_COPY = 'test-tx-queue-copy; {create: always}'
     txssn = self.conn.session(transactional=True)
     contents = self.send(self.ssn, TX_Q, "txTest", 3)
     txrcv = txssn.receiver(TX_Q)
@@ -376,7 +376,7 @@
     self.txTest(False)
 
   def txTestSend(self, commit):
-    TX_SEND_Q = 'test-tx-send-queue {create: always}'
+    TX_SEND_Q = 'test-tx-send-queue; {create: always}'
     txssn = self.conn.session(transactional=True)
     contents = self.send(txssn, TX_SEND_Q, "txTestSend", 3)
     rcv = self.ssn.receiver(TX_SEND_Q)
@@ -399,7 +399,7 @@
     self.txTestSend(False)
 
   def txTestAck(self, commit):
-    TX_ACK_Q = 'test-tx-ack-queue {create: always}'
+    TX_ACK_Q = 'test-tx-ack-queue; {create: always}'
     txssn = self.conn.session(transactional=True)
     txrcv = txssn.receiver(TX_ACK_Q)
     self.assertEmpty(txrcv)
@@ -444,7 +444,7 @@
     except Disconnected:
       pass
 
-RECEIVER_Q = 'test-receiver-queue {create: always}'
+RECEIVER_Q = 'test-receiver-queue; {create: always}'
 
 class ReceiverTests(Base):
 
@@ -581,7 +581,7 @@
   # XXX: need testClose
 
 NOSUCH_Q = "this-queue-should-not-exist"
-UNPARSEABLE_ADDR = "{bad address}"
+UNPARSEABLE_ADDR = "name/subject; {bad options"
 UNLEXABLE_ADDR = "\0x0\0x1\0x2\0x3"
 
 class AddressErrorTests(Base):
@@ -630,24 +630,24 @@
   def testUnparseableTarget(self):
     # XXX: should have specific exception for this
     self.sendErrorTest(UNPARSEABLE_ADDR, SendError,
-                       lambda e: "expecting ID" in str(e))
+                       lambda e: "expecting COLON" in str(e))
 
   def testUnparseableSource(self):
     # XXX: should have specific exception for this
     self.fetchErrorTest(UNPARSEABLE_ADDR, ReceiveError,
-                        lambda e: "expecting ID" in str(e))
+                        lambda e: "expecting COLON" in str(e))
 
   def testUnlexableTarget(self):
     # XXX: should have specific exception for this
     self.sendErrorTest(UNLEXABLE_ADDR, SendError,
-                       lambda e: "unrecognized character" in str(e))
+                       lambda e: "unrecognized characters" in str(e))
 
   def testUnlexableSource(self):
     # XXX: should have specific exception for this
     self.fetchErrorTest(UNLEXABLE_ADDR, ReceiveError,
-                        lambda e: "unrecognized character" in str(e))
+                        lambda e: "unrecognized characters" in str(e))
 
-SENDER_Q = 'test-sender-q {create: always}'
+SENDER_Q = 'test-sender-q; {create: always}'
 
 class SenderTests(Base):
 
@@ -756,7 +756,7 @@
     m.content = u"<html/>"
     assert m.content_type == "text/html; charset=utf8"
 
-ECHO_Q = 'test-message-echo-queue {create: always}'
+ECHO_Q = 'test-message-echo-queue; {create: always}'
 
 class MessageEchoTests(Base):
 



---------------------------------------------------------------------
Apache Qpid - AMQP Messaging Implementation
Project:      http://qpid.apache.org
Use/Interact: mailto:commits-subscribe@qpid.apache.org