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): def marked(self):
return "%s\n%s"%(self.s, " "*(self.col-1) + "^") return "%s\n%s"%(self.s, " "*(self.col-1) + "^")
def __str__(self):
return self.msg
class ServerError(Exception): pass class ServerError(Exception): pass
@ -41,16 +44,17 @@ v_integer = pp.Regex(r"[+-]?\d+")\
.setName("integer")\ .setName("integer")\
.setParseAction(lambda toks: int(toks[0])) .setParseAction(lambda toks: int(toks[0]))
v_string = pp.MatchFirst(
v_literal = pp.MatchFirst(
[ [
pp.QuotedString("\"", escChar="\\", unquoteResults=True), pp.QuotedString("\"", escChar="\\", unquoteResults=True),
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 ",:")) pp.Word("".join(i for i in pp.printables if i not in ",:"))
] ]
) )
@ -121,6 +125,13 @@ class ValueLiteral:
return self.val return self.val
class ValueNakedLiteral(ValueLiteral):
@classmethod
def expr(klass):
e = v_naked_literal.copy()
return e.setParseAction(lambda x: klass(*x))
class ValueGenerate: class ValueGenerate:
UNITS = dict( UNITS = dict(
b = 1024**0, b = 1024**0,
@ -163,7 +174,7 @@ class ValueFile:
@classmethod @classmethod
def expr(klass): def expr(klass):
e = pp.Literal("<").suppress() e = pp.Literal("<").suppress()
e = e + v_literal e = e + v_naked_literal
return e.setParseAction(lambda x: klass(*x)) return e.setParseAction(lambda x: klass(*x))
def get_generator(self, settings): def get_generator(self, settings):
@ -197,7 +208,7 @@ class Body:
@classmethod @classmethod
def expr(klass): def expr(klass):
e = pp.Literal("b:").suppress() e = pp.Literal("b").suppress()
e = e + Value e = e + Value
return e.setParseAction(lambda x: klass(*x)) return e.setParseAction(lambda x: klass(*x))
@ -208,7 +219,7 @@ class _Pause:
@classmethod @classmethod
def expr(klass): def expr(klass):
e = pp.Literal("p%s:"%klass.sub).suppress() e = pp.Literal("p%s"%klass.sub).suppress()
e = e + pp.MatchFirst( e = e + pp.MatchFirst(
[ [
v_integer, v_integer,
@ -273,7 +284,7 @@ class Header:
@classmethod @classmethod
def expr(klass): def expr(klass):
e = pp.Literal("h:").suppress() e = pp.Literal("h").suppress()
e += Value e += Value
e += pp.Literal(":").suppress() e += pp.Literal(":").suppress()
e += Value e += Value
@ -294,7 +305,7 @@ class Code:
def expr(klass): def expr(klass):
e = v_integer e = v_integer
e = e + pp.Optional( e = e + pp.Optional(
pp.Literal(":").suppress() + Value Value
) )
return e.setParseAction(lambda x: klass(*x)) 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: Value Specifiers:
!500k - 500k of random data !500k - 500k of random data
!500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable !500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable
"foo" - literal "foo" - literal
foo - literal
<path - load from path under data directory <path - load from path under data directory
<"path literal" - load from path under data directory <"path" - 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
Examples: Examples:
200,b:500k 200,b500k
404,pb5,b1k:printable
404,p:5s,b:1k:printable 200,t"text/json",pr5,b1k
200,b1k,xr
200,t:text/json,p:5s,b:1k
200,b:1k,xr
Sequences: Sequences:
200 * 2 | !forever 200 * 2 | !forever
200 | 404 | 200,b:500g 200 | 404 | 200,b:500g
Anchors: Anchors:
Passed on command-line? Passed on command-line?

View File

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