Make specification language more terse, and more regular.

This commit is contained in:
Aldo Cortesi 2012-04-28 17:12:39 +12:00
parent 5fc2a63781
commit a779aac9db
3 changed files with 79 additions and 108 deletions

View File

@ -15,6 +15,9 @@ class ParseException(Exception):
def marked(self):
return "%s\n%s"%(self.s, " "*(self.col-1) + "^")
def __str__(self):
return self.msg
class ServerError(Exception): pass
@ -41,16 +44,17 @@ v_integer = pp.Regex(r"[+-]?\d+")\
.setName("integer")\
.setParseAction(lambda toks: int(toks[0]))
v_string = pp.MatchFirst(
v_literal = pp.MatchFirst(
[
pp.QuotedString("\"", escChar="\\", unquoteResults=True),
pp.QuotedString("'", escChar="\\", unquoteResults=True),
]
)
v_literal = pp.MatchFirst(
v_naked_literal = pp.MatchFirst(
[
v_string,
v_literal,
pp.Word("".join(i for i in pp.printables if i not in ",:"))
]
)
@ -121,6 +125,13 @@ class ValueLiteral:
return self.val
class ValueNakedLiteral(ValueLiteral):
@classmethod
def expr(klass):
e = v_naked_literal.copy()
return e.setParseAction(lambda x: klass(*x))
class ValueGenerate:
UNITS = dict(
b = 1024**0,
@ -163,7 +174,7 @@ class ValueFile:
@classmethod
def expr(klass):
e = pp.Literal("<").suppress()
e = e + v_literal
e = e + v_naked_literal
return e.setParseAction(lambda x: klass(*x))
def get_generator(self, settings):
@ -197,7 +208,7 @@ class Body:
@classmethod
def expr(klass):
e = pp.Literal("b:").suppress()
e = pp.Literal("b").suppress()
e = e + Value
return e.setParseAction(lambda x: klass(*x))
@ -208,7 +219,7 @@ class _Pause:
@classmethod
def expr(klass):
e = pp.Literal("p%s:"%klass.sub).suppress()
e = pp.Literal("p%s"%klass.sub).suppress()
e = e + pp.MatchFirst(
[
v_integer,
@ -273,7 +284,7 @@ class Header:
@classmethod
def expr(klass):
e = pp.Literal("h:").suppress()
e = pp.Literal("h").suppress()
e += Value
e += pp.Literal(":").suppress()
e += Value
@ -294,7 +305,7 @@ class Code:
def expr(klass):
e = v_integer
e = e + pp.Optional(
pp.Literal(":").suppress() + Value
Value
)
return e.setParseAction(lambda x: klass(*x))

103
notes
View File

@ -1,92 +1,53 @@
Response:
code[msg],[comma-separated features]
Features:
hVALUE:VALUE Set header
bVALUE Set body
db Disconnect before sending data
dr Disconnect randomly
pbTIME Pause before sending data for NUM seconds or forever
paTIME Pause after sending all data for NUM seconds or forever
prTIME Pause randomly for NUM seconds or forever
cVALUE Set Content-Type header
lVALUE Set Location header
Time Specifiers:
5 - 5 seconds
5s - 5 seconds
5m - 5 minutes
5h - 5 hours
Value Specifiers:
!500k - 500k of random data
!500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable
"foo" - literal
foo - literal
<path - load from path under data directory
<"path literal" - load from path under data directory
Response:
code:[comma-separated features]
Specifying a response:
Code 200:
code:msg
200:VALUE
Headers:
h:VALUE:VALUE
Body:
b:VALUE
Pauses:
pb:5
pb:forever
Pause before sending data:
pb:5
Pause after sending data, before hanging up:
pa:5
Pause after sending a random amoutn of data:
pr:5
Disconnect:
Before sending data:
db
Randomly:
dr
Shortcuts:
Redirects:
code:msg>destination
code>destination
301:VALUE>VALUE
301>http://foo.bar
Content-type:
t:content-type
<"path" - load from path under data directory
Examples:
200,b:500k
404,p:5s,b:1k:printable
200,t:text/json,p:5s,b:1k
200,b:1k,xr
200,b500k
404,pb5,b1k:printable
200,t"text/json",pr5,b1k
200,b1k,xr
Sequences:
200 * 2 | !forever
200 | 404 | 200,b:500g
Anchors:
Passed on command-line?

View File

@ -17,7 +17,7 @@ class DummyRequest(StringIO.StringIO):
class uMisc(libpry.AutoTree):
def test_generators(self):
v = rparse.Value.parseString("val")[0]
v = rparse.Value.parseString("'val'")[0]
g = v.get_generator({})
assert g[:] == "val"
@ -94,26 +94,25 @@ class uMisc(libpry.AutoTree):
assert v.datatype == "digits"
def test_value(self):
assert rparse.Value.parseString("val")[0].val == "val"
assert rparse.Value.parseString("'val'")[0].val == "val"
assert rparse.Value.parseString('"val"')[0].val == "val"
assert rparse.Value.parseString('"\'val\'"')[0].val == "'val'"
def test_body(self):
e = rparse.Body.expr()
v = e.parseString("b:foo")[0]
v = e.parseString("b'foo'")[0]
assert v.value.val == "foo"
v = e.parseString("b:!100")[0]
v = e.parseString("b!100")[0]
assert str(v.value) == "!100b:bytes"
v = e.parseString("b:!100g:digits", parseAll=True)[0]
v = e.parseString("b!100g:digits", parseAll=True)[0]
assert v.value.datatype == "digits"
assert str(v.value) == "!100g:digits"
def test_header(self):
e = rparse.Header.expr()
v = e.parseString("h:foo:bar")[0]
v = e.parseString("h'foo':'bar'")[0]
assert v.key.val == "foo"
assert v.value.val == "bar"
@ -122,17 +121,17 @@ class uMisc(libpry.AutoTree):
v = e.parseString("200")[0]
assert v.code == 200
v = e.parseString("404:msg")[0]
v = e.parseString("404'msg'")[0]
assert v.code == 404
assert v.msg.val == "msg"
r = e.parseString("200:'foo'")[0]
r = e.parseString("200'foo'")[0]
assert r.msg.val == "foo"
r = e.parseString("200:'\"foo\"'")[0]
r = e.parseString("200'\"foo\"'")[0]
assert r.msg.val == "\"foo\""
r = e.parseString('200:"foo"')[0]
r = e.parseString('200"foo"')[0]
assert r.msg.val == "foo"
r = e.parseString('404')[0]
@ -168,20 +167,20 @@ class uDisconnects(libpry.AutoTree):
class uPauses(libpry.AutoTree):
def test_before(self):
e = rparse.PauseBefore.expr()
v = e.parseString("pb:10")[0]
v = e.parseString("pb10")[0]
assert v.value == 10
v = e.parseString("pb:forever")[0]
v = e.parseString("pbforever")[0]
assert v.value == "forever"
def test_after(self):
e = rparse.PauseAfter.expr()
v = e.parseString("pa:10")[0]
v = e.parseString("pa10")[0]
assert v.value == 10
def test_random(self):
e = rparse.PauseRandom.expr()
v = e.parseString("pr:10")[0]
v = e.parseString("pr10")[0]
assert v.value == 10
@ -191,41 +190,41 @@ class uparse(libpry.AutoTree):
try:
rparse.parse({}, "400:msg,b:")
except rparse.ParseException, v:
print v.marked()
assert v.marked()
def test_parse_header(self):
r = rparse.parse({}, "400,h:foo:bar")
r = rparse.parse({}, '400,h"foo":"bar"')
assert r.get_header("foo") == "bar"
def test_parse_pause_before(self):
r = rparse.parse({}, "400,pb:10")
r = rparse.parse({}, "400,pb10")
assert (0, 10) in r.pauses
def test_parse_pause_after(self):
r = rparse.parse({}, "400,pa:10")
r = rparse.parse({}, "400,pa10")
assert (sys.maxint, 10) in r.pauses
def test_parse_pause_random(self):
r = rparse.parse({}, "400,pr:10")
r = rparse.parse({}, "400,pr10")
assert ("random", 10) in r.pauses
class uResponse(libpry.AutoTree):
def dummy_response(self):
return rparse.parse({}, "400:msg")
return rparse.parse({}, "400'msg'")
def test_response(self):
r = rparse.parse({}, "400:msg")
r = rparse.parse({}, "400'msg'")
assert r.code == 400
assert r.msg == "msg"
r = rparse.parse({}, "400:msg,b:!100b")
r = rparse.parse({}, "400'msg',b!100b")
assert r.msg == "msg"
assert r.body[:]
assert str(r)
def test_ready_randoms(self):
r = rparse.parse({}, "400:msg")
r = rparse.parse({}, "400'msg'")
x = [(0, 5)]
assert r.ready_randoms(100, x) == x
@ -246,7 +245,7 @@ class uResponse(libpry.AutoTree):
def test_write_values(self):
tst = "foo"*1025
r = rparse.parse({}, "400:msg")
r = rparse.parse({}, "400'msg'")
s = DummyRequest()
r.write_values(s, [tst], [], None)
@ -254,7 +253,7 @@ class uResponse(libpry.AutoTree):
def test_write_values_pauses(self):
tst = "".join(str(i) for i in range(10))
r = rparse.parse({}, "400:msg")
r = rparse.parse({}, "400'msg'")
for i in range(2, 10):
s = DummyRequest()
@ -274,7 +273,7 @@ class uResponse(libpry.AutoTree):
def test_render(self):
s = DummyRequest()
r = rparse.parse({}, "400:msg")
r = rparse.parse({}, "400'msg'")
r.render(s)
def test_length(self):
@ -282,9 +281,9 @@ class uResponse(libpry.AutoTree):
s = DummyRequest()
x.render(s)
assert x.length() == len(s.getvalue())
testlen(rparse.parse({}, "400:msg"))
testlen(rparse.parse({}, "400:msg,h:foo:bar"))
testlen(rparse.parse({}, "400:msg,h:foo:bar,b:!100b"))
testlen(rparse.parse({}, "400'msg'"))
testlen(rparse.parse({}, "400'msg',h'foo':'bar'"))
testlen(rparse.parse({}, "400'msg',h'foo':'bar',b!100b"))
tests = [