Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
valyala committed Nov 30, 2015
2 parents 2c1a83a + 48f0168 commit 16106d3
Show file tree
Hide file tree
Showing 14 changed files with 885 additions and 697 deletions.
16 changes: 6 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,24 +177,20 @@ env:
- "TESTDIR=Ur/urweb"

before_install:
# Need to install python modules before using
# python
- pip install -r requirements.txt
- pip install colorama==0.3.1
# Version 2.3 has a nice Counter() and other features
# but it requires —-allow-external and -—allow-unverified
- pip install progressbar==2.2
- pip install requests

install:
# Configure Travis-CI build environment for TFB
# e.g. setup databases, users, etc
- ./toolset/run-ci.py cisetup "$TESTDIR"

addons:
postgresql: "9.3"

install:
# Install prerequisites
- ./toolset/run-ci.py prereq "$TESTDIR"

# Install software for this framework
- ./toolset/run-ci.py install "$TESTDIR"

script:
# Pick one test in this directory and verify
- time ./toolset/run-ci.py verify "$TESTDIR"
1 change: 0 additions & 1 deletion deployment/vagrant-common/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ if [ ! -e "~/.firstboot" ]; then
echo "Cloning project from $GH_REPO $GH_BRANCH"
git clone -b ${GH_BRANCH} https://github.com/${GH_REPO}.git $FWROOT
fi
sudo pip install -r $FWROOT/requirements.txt

# Everyone gets SSH access to localhost
echo "Setting up SSH access to localhost"
Expand Down
4 changes: 2 additions & 2 deletions frameworks/Crystal/moonshine/server-redis.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ app = App.new

class CONTENT
UTF8 = "; charset=UTF-8"
JSON = "application/json" + UTF8
JSON = "application/json" #+ UTF8
PLAIN = "text/plain"
HTML = "text/html" + UTF8
HTML = "text/html" #+ UTF8
end

ID_MAXIMUM = 10_000
Expand Down
303 changes: 155 additions & 148 deletions toolset/benchmark/framework_test.py

Large diffs are not rendered by default.

160 changes: 49 additions & 111 deletions toolset/benchmark/test_types/db_type.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,55 @@
from benchmark.test_types.framework_test_type import FrameworkTestType
from benchmark.test_types.verifications import basic_body_verification, verify_headers, verify_randomnumber_object

import json

class DBTestType(FrameworkTestType):
def __init__(self):
args = ['db_url']
FrameworkTestType.__init__(self, name='db',
accept_header=self.accept_json,
requires_db=True, args=args)

def get_url(self):
return self.db_url

def verify(self, base_url):
'''Ensures body is valid JSON with a key 'id' and a key
'randomNumber', both of which must map to integers
'''

url = base_url + self.db_url
full_response = self._curl(url)
body = self._curl_body(url)

# Empty response
if body is None:
return [('fail','No response', url)]
elif len(body) == 0:
return [('fail','Empty Response', url)]

# Valid JSON?
try:
response = json.loads(body)
except ValueError as ve:
return [('fail',"Invalid JSON - %s" % ve, url)]

problems = []

# We are allowing the single-object array
# e.g. [{'id':5, 'randomNumber':10}] for now,
# but will likely make this fail at some point
if type(response) == list:
response = response[0]
problems.append( ('warn', 'Response is a JSON array. Expected JSON object (e.g. [] vs {})', url) )

# Make sure there was a JSON object inside the array
if type(response) != dict:
problems.append( ('fail', 'Response is not a JSON object or an array of JSON objects', url) )
return problems

problems += self._verifyObject(response, url)

# Ensure required response headers are present
if any(v.lower() not in full_response.lower() for v in ('Server','Date','Content-Type: application/json')) \
or all(v.lower() not in full_response.lower() for v in ('Content-Length','Transfer-Encoding')):
problems.append( ('warn','Required response header missing.',url) )

if len(problems) == 0:
return [('pass','',url)]
else:
return problems

def _verifyObject(self, db_object, url, max_infraction='fail'):
'''Ensure the passed item is a JSON object with
keys 'id' and 'randomNumber' mapping to ints.
Separate method allows the QueryTestType to
reuse these checks'''

problems = []

if type(db_object) != dict:
got = str(db_object)[:20]
if len(str(db_object)) > 20:
got = str(db_object)[:17] + '...'
return [(max_infraction, "Expected a JSON object, got '%s' instead" % got, url)]

# Make keys case insensitive
db_object = {k.lower(): v for k,v in db_object.iteritems()}

if "id" not in db_object:
problems.append( (max_infraction, "Response has no 'id' key", url) )
if "randomnumber" not in db_object:
problems.append( (max_infraction, "Response has no 'randomNumber' key", url) )

# Ensure we can continue on to use these keys
if "id" not in db_object or "randomnumber" not in db_object:
return problems

try:
float(db_object["id"])
except ValueError as ve:
problems.append( (max_infraction, "Response key 'id' does not map to a number - %s" % ve, url) )

try:
float(db_object["randomnumber"])
except ValueError as ve:
problems.append( (max_infraction, "Response key 'randomNumber' does not map to a number - %s" % ve, url) )

if type(db_object["id"]) != int:
problems.append( ('warn', '''Response key 'id' contains extra quotations or decimal points.
This may negatively affect performance during benchmarking''', url) )

# Tests based on the value of the numbers
try:
response_id = float(db_object["id"])
response_rn = float(db_object["randomnumber"])

if response_id > 10000 or response_id < 1:
problems.append( ('warn', "Response key 'id' should be between 1 and 10,000", url) )

if response_rn > 10000:
problems.append( ('warn', '''Response key 'randomNumber' is over 10,000. This may negatively
afect performance by sending extra bytes.''', url) )
except ValueError:
pass

return problems
class DBTestType(FrameworkTestType):

def __init__(self):
kwargs = {
'name': 'db',
'accept_header': self.accept('json'),
'requires_db': True,
'args': ['db_url']
}
FrameworkTestType.__init__(self, **kwargs)

def get_url(self):
return self.db_url

def verify(self, base_url):
'''Ensures body is valid JSON with a key 'id' and a key
'randomNumber', both of which must map to integers
'''

url = base_url + self.db_url
headers, body = self.request_headers_and_body(url)

response, problems = basic_body_verification(body)

if len(problems) > 0:
return problems

# We are allowing the single-object array
# e.g. [{'id':5, 'randomNumber':10}] for now,
# but will likely make this fail at some point
if type(response) == list:
response = response[0]
problems.append(
('warn', 'Response is a JSON array. Expected JSON object (e.g. [] vs {})', url))

# Make sure there was a JSON object inside the array
if type(response) != dict:
problems.append(
('fail', 'Response is not a JSON object or an array of JSON objects', url))
return problems

# Verify response content
problems += verify_randomnumber_object(response, url)
problems += verify_headers(headers, url, should_be='json')

if len(problems) == 0:
return [('pass', '', url)]
else:
return problems
150 changes: 84 additions & 66 deletions toolset/benchmark/test_types/fortune_type.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,89 @@
from benchmark.test_types.framework_test_type import FrameworkTestType
from benchmark.fortune_html_parser import FortuneHTMLParser
from benchmark.test_types.verifications import basic_body_verification, verify_headers


class FortuneTestType(FrameworkTestType):
def __init__(self):
args = ['fortune_url']
FrameworkTestType.__init__(self, name='fortune',
requires_db=True,
accept_header=self.accept_html,
args=args)

def get_url(self):
return self.fortune_url

def verify(self, base_url):
'''Parses the given HTML string and asks the
FortuneHTMLParser whether the parsed string is a
valid fortune response
'''
url = base_url + self.fortune_url
full_response = self._curl(url)
body = self._curl_body(url)

# Empty response
if body is None:
return [('fail','No response', url)]
elif len(body) == 0:
return [('fail','Empty Response', url)]

parser = FortuneHTMLParser()
parser.feed(body)
(valid, diff) = parser.isValidFortune(self.out)
if valid:
# Ensure required response headers are present
if any(v.lower() not in full_response.lower() for v in ('Server','Date','Content-Type: text/html')) \
or all(v.lower() not in full_response.lower() for v in ('Content-Length','Transfer-Encoding')):
return[('warn','Required response header missing.',url)]

return [('pass','',url)]
else:
failures = [('fail','Invalid according to FortuneHTMLParser',url)]
# Catch exceptions because we are relying on internal code
try:
# Parsing this:
# --- Valid
# +++ Response
# @@ -1 +1 @@
#
# -<!doctype html><html><head><title>Fortunes</title></head><body><table>
# +<!doctype html><html><head><meta></meta><title>Fortunes</title></head><body><div><table>
# @@ -16 +16 @@

current_neg = []
current_pos = []
for line in diff[3:]:
if line[0] == '+':
current_neg.append(line[1:])
elif line[0] == '-':
current_pos.append(line[1:])
elif line[0] == '@':
failures.append( ('fail',
"`%s` should be `%s`" % (''.join(current_neg), ''.join(current_pos)),
url) )
if len(current_pos) != 0:
failures.append( ('fail',
"`%s` should be `%s`" % (''.join(current_neg), ''.join(current_pos)),
url) )
except:
pass
return failures

def __init__(self):
kwargs = {
'name': 'fortune',
'accept_header': self.accept('html'),
'requires_db': True,
'args': ['fortune_url']
}
FrameworkTestType.__init__(self, **kwargs)

def get_url(self):
return self.fortune_url

def verify(self, base_url):
'''Parses the given HTML string and asks the
FortuneHTMLParser whether the parsed string is a
valid fortune response
'''

url = base_url + self.fortune_url
headers, body = self.request_headers_and_body(url)

_, problems = basic_body_verification(body, is_json_check=False)

if len(problems) > 0:
return problems

parser = FortuneHTMLParser()
parser.feed(body)
(valid, diff) = parser.isValidFortune(self.out)

if valid:
problems += verify_headers(headers, url, should_be='html')

if len(problems) == 0:
return [('pass', '', url)]
else:
return problems
else:
failures = []
failures.append(
('fail', 'Invalid according to FortuneHTMLParser', url))
failures += self._parseDiffForFailure(diff, failures, url)
return failures

def _parseDiffForFailure(self, diff, failures, url):
'''Example diff:
--- Valid
+++ Response
@@ -1 +1 @@
-<!doctype html><html><head><title>Fortunes</title></head><body><table>
+<!doctype html><html><head><meta></meta><title>Fortunes</title></head><body><div><table>
@@ -16 +16 @@
'''

problems = []

# Catch exceptions because we are relying on internal code
try:
current_neg = []
current_pos = []
for line in diff[3:]:
if line[0] == '+':
current_neg.append(line[1:])
elif line[0] == '-':
current_pos.append(line[1:])
elif line[0] == '@':
problems.append(('fail',
"`%s` should be `%s`" % (
''.join(current_neg), ''.join(current_pos)),
url))
if len(current_pos) != 0:
problems.append(
('fail',
"`%s` should be `%s`" % (
''.join(current_neg), ''.join(current_pos)),
url))
except:
# If there were errors reading the diff, then no diff information
pass
return problems
Loading

0 comments on commit 16106d3

Please sign in to comment.