Skip to content

Commit

Permalink
examples of concurrent testing scripts for api using python sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
mkochanowicz committed Feb 22, 2018
1 parent 1d7c4dd commit a5d18d1
Show file tree
Hide file tree
Showing 4 changed files with 525 additions and 0 deletions.
104 changes: 104 additions & 0 deletions python_scripts/tests/api_tests/jsonsocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import json
import socket

class JSONSocket(object):
"""
Class encapsulates socket object with special handling json rpc.
timeout is ignored now - nonblicking socket not supported.
"""
def __init__(self, host, port, path, timeout=None):
"""
host in form [http[s]://]<ip_address>[:port]
if port not in host must be spefified as argument
"""
if host.find("http://") == 0 or host.find("https://") == 0:
host = host[host.find("//")+2 : len(host)]
_host = host
if port != None:
host = host + ":" + str(port)
else:
colon_pos = host.rfind(":")
_host = host[0 : colon_pos]
port = int(host[colon_pos+1 : len(host)])
self.__fullpath = host + path
self.__host = bytes(host, "utf-8")
self.__path = bytes(path, "utf-8")
#print("<host>: {}; <port>: {}".format(_host, port))
self.__sock = socket.create_connection((_host, port))
self.__sock.setblocking(True)
#self.__sock.settimeout(timeout)
self.__head = b"POST " + self.__path + b" HTTP/1.0\r\n" + \
b"HOST: " + self.__host + b"\r\n" + \
b"Content-type: application/json\r\n"

def get_fullpath(self):
return self.__fullpath

def request(self, data=None, json=None):
"""
data - complete binary form of json request (ends with '\r\n'
json - json request as python dict
return value in form of json response as python dict
"""
if data == None:
data = bytes(json.dumps(json), "utf-8") + b"\r\n"
length = bytes(str(len(data)), "utf-8")
request = self.__head + \
b"Content-length: " + length + b"\r\n\r\n" + \
data
#print("request:", request.decode("utf-8"))
self.__sock.sendall(request)
#self.__sock.send(request)
status, response = self.__read()
#if response == {}:
# print("response is empty for request:", request.decode("utf-8"))
return status, response

def __call__(self, data=None, json=None):
return self.request(data, json)

def __read(self):
response = ''
while True:
temp = self.__sock.recv(4096)
if not temp: break
response += temp.decode("utf-8")

if response.find("HTTP") == 0:
response = response[response.find("\r\n\r\n")+4 : len(response)]
if response and response != '':
r = json.loads(response)
if 'result' in r:
return True, r
else:
return False, r

return False, {}

def __del__(self):
if self.__sock:
self.__sock.close()


def steemd_call(host, data=None, json=None, max_tries=10, timeout=0.1):
"""
host - [http[s]://<ip_address>:<port>
data - binary form of request body, if missing json object should be provided (as python dict/array)
"""
try:
jsocket = JSONSocket(host, None, "/rpc", timeout)
except:
print("Cannot open socket for:", host)
return False, {}

for i in range(max_tries):
try:
status, response = jsocket(data, json)
if status:
return status, response
except:
continue
else:
return False, {}
78 changes: 78 additions & 0 deletions python_scripts/tests/api_tests/list_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Create list of all steem accounts in file.
Usage: create_account_list.py <server_address> [<output_filename>]
"""
import sys
import json
from jsonsocket import JSONSocket
from jsonsocket import steemd_call

def list_accounts(url):
"""
url in form <ip_address>:<port>
"""
last_account = ""
end = False
accounts_count = 0
accounts = []

while end == False:
request = bytes( json.dumps( {
"jsonrpc": "2.0",
"id": 0,
"method": "database_api.list_accounts",
"params": { "start": last_account, "limit": 1000, "order": "by_name" }
} ), "utf-8" ) + b"\r\n"

status, response = steemd_call(url, data=request)

if status == False:
print( "rpc failed for last_account: " + last_account )
return []

account_list = response["result"]["accounts"]

if last_account != "":
assert account_list[0]["name"] == last_account
del account_list[0]

if len( account_list ) == 0:
end = True
continue

last_account = account_list[-1]["name"]
accounts_count += len( accounts )
for account in account_list:
accounts.append( account["name"] )

# while end == False
return accounts


def main():
if len( sys.argv ) < 2 or len( sys.argv ) > 3:
exit( "Usage: create_account_list.py <server_address> [<output_filename>]" )

url = sys.argv[1]
print( url )

accounts = list_accounts( url )

if len(accounts) == 0:
exit(-1)

if len( sys.argv ) == 3:
filename = sys.argv[2]

try: file = open( filename, "w" )
except: exit( "Cannot open file " + filename )

for account in accounts:
file.write(account + "\n")

file.close()


if __name__ == "__main__":
main()
172 changes: 172 additions & 0 deletions python_scripts/tests/api_tests/test_ah_get_account_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
Usage: script_name jobs url1 url2 [working_dir [accounts_file]]
Example: script_name 4 http://127.0.0.1:8090 http://127.0.0.1:8091 [get_account_history [accounts]]
set jobs to 0 if you want use all processors
url1 is reference url for list_accounts
"""
import sys
import json
import os
import shutil
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import Future
from concurrent.futures import wait
from jsonsocket import JSONSocket
from jsonsocket import steemd_call
from list_account import list_accounts
from pathlib import Path


wdir = Path()
errors = 0


def main():
if len( sys.argv ) < 4 or len( sys.argv ) > 6:
print( "Usage: script_name jobs url1 url2 [working_dir [accounts_file]]" )
print( " Example: script_name 4 http://127.0.0.1:8090 http://127.0.0.1:8091 [get_account_history [accounts]]" )
print( " set jobs to 0 if you want use all processors" )
print( " url1 is reference url for list_accounts" )
exit ()

global wdir
global errors

jobs = int(sys.argv[1])
if jobs <= 0:
import multiprocessing
jobs = multiprocessing.cpu_count()

url1 = sys.argv[2]
url2 = sys.argv[3]

if len( sys.argv ) > 4:
wdir = Path(sys.argv[4])

accounts_file = sys.argv[5] if len( sys.argv ) > 5 else ""

print( "setup:" )
print( " jobs: {}".format(jobs) )
print( " url1: {}".format(url1) )
print( " url2: {}".format(url2) )
print( " wdir: {}".format(wdir) )
print( " accounts_file: {}".format(accounts_file) )

if accounts_file != "":
try:
with open(accounts_file, "r") as file:
accounts = [account[:-1] for account in file]
except:
exit("Cannot open file: " + accounts_file)
else:
accounts = list_accounts(url1)

length = len(accounts)

if length == 0:
exit("There are no any account!")

create_wdir()

print( str(length) + " accounts" )

if jobs > 1:
first = 0
last = length - 1
accounts_per_job = length // jobs

with ProcessPoolExecutor(max_workers=jobs) as executor:
for i in range(jobs-1):
executor.submit(compare_results, url1, url2, accounts[first : first+accounts_per_job-1])
first = first + accounts_per_job
executor.submit(compare_results, url1, url2, accounts[first : last])
else:
compare_results(url1, url2, accounts)

return errors


def create_wdir():
global wdir

if wdir.exists():
if wdir.is_file():
os.remove(wdir)

if wdir.exists() == False:
wdir.mkdir(parents=True)


def compare_results(url1, url2, accounts, max_tries=10, timeout=0.1):
success = True
print("Compare accounts: [{}..{}]".format(accounts[0], accounts[-1]))

for account in accounts:
if get_account_history(url1, url2, account, max_tries, timeout) == False:
success = False; break

print("Compare accounts: [{}..{}] {}".format(accounts[0], accounts[-1], "finished" if success else "break with error" ))


def get_account_history(url1, url2, account, max_tries=10, timeout=0.1):
global wdir
START = -1
HARD_LIMIT = 10000
LIMIT = HARD_LIMIT

while True:
request = bytes( json.dumps( {
"jsonrpc": "2.0",
"id": 0,
"method": "account_history_api.get_account_history",
"params": { "account": account, "start": START, "limit": LIMIT }
} ), "utf-8" ) + b"\r\n"

with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(steemd_call, url1, data=request, max_tries=max_tries, timeout=timeout)
future2 = executor.submit(steemd_call, url2, data=request, max_tries=max_tries, timeout=timeout)

status1, json1 = future1.result()
status2, json2 = future2.result()
#status1, json1 = steemd_call(url1, data=request, max_tries=max_tries, timeout=timeout)
#status2, json2 = steemd_call(url2, data=request, max_tries=max_tries, timeout=timeout)

if status1 == False or status2 == False or json1 != json2:
print("Comparison failed for account: {}; start: {}; limit: {}".format(account, START, LIMIT))
filename = wdir / account
++errors
try: file = filename.open("w")
except: print("Cannot open file:", filename); return False

file.write("Comparison failed:\n")
file.write("{} response:\n".format(url1))
json.dump(json1, file, indent=2, sort_keys=True)
file.write("\n")
file.write("{} response:\n".format(url2))
json.dump(json2, file, indent=2, sort_keys=True)
file.write("\n")
file.close()
return False

history = json1["result"]["history"]
last = history[0][0] if len(history) else 0

if last == 0: break

START = last

if START > HARD_LIMIT:
LIMIT = HARD_LIMIT
if last > HARD_LIMIT:
LIMIT = HARD_LIMIT
else:
LIMIT = last
# while True

return True


if __name__ == "__main__":
main()
Loading

0 comments on commit a5d18d1

Please sign in to comment.