Skip to content

Commit

Permalink
fire ammo if no reload in prepare
Browse files Browse the repository at this point in the history
  • Loading branch information
andgineer committed Apr 21, 2019
1 parent fa89154 commit 61dfc7c
Show file tree
Hide file tree
Showing 15 changed files with 145 additions and 46 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
recursive-include bombard/examples *.py *.yaml
include bombard/LICENSE.txt
global-exclude __init__.py

File renamed without changes.
5 changes: 3 additions & 2 deletions bombard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
__version__ = '1.8.1'
__version__ = '1.9.1'


def version():
""" 'major.minor' without build number """
return '.'.join(__version__.split('.')[:2])


if __name__ == '__main__':
print(version())
print(version())
4 changes: 4 additions & 0 deletions bombard/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def get_args():
'--verbose', '-v', dest='verbose', default=False, action='store_true',
help=f'verbose output (by default False)'
)
parser.add_argument(
'--version', dest='version', default=False, action='store_true',
help=f'bombard version'
)
parser.add_argument(
'--log', '-l', dest='log', type=str, default=None,
help=f'log file name'
Expand Down
20 changes: 8 additions & 12 deletions bombard/bombardier.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, supply: dict=None, args=None, campaign_book: dict=None, ok_st
self.campaign = campaign_book
self.ok = ok_statuses if ok_statuses is not None else DEFAULT_OK
self.overload = overload_statuses if overload_statuses is not None else DEFAULT_OVERLOAD
self.request_fired = False # from any request reload() was called

self.show_request = {
1: 'Sent 1st request..'
Expand Down Expand Up @@ -98,12 +99,6 @@ def process_resp(self, ammo: dict, status: int, resp: str, elapsed: int, size: i
self.reporter.log(True, elapsed, request.get('name'), size)
log.debug(f'{status} reply\n{resp}')
if 'extract' in request:
#todo: auto fire ammo after prepare if no reload
# now extract option is not so useful - you extract something but do not add
# requests that use that. you cannot just have this requests in the same
# section because of unpredictable requests order
# so we wait prepare section scripts to finish and fire ammo section IF no reload
# was registered
try:
data = json.loads(resp)
if not hasattr(request['extract'], 'items'):
Expand All @@ -115,10 +110,6 @@ def process_resp(self, ammo: dict, status: int, resp: str, elapsed: int, size: i
self.supply[name] = eval('data' + extractor)
else:
self.supply[name] = data[extractor]
if not isinstance(request['reload'], list):
request['reload'] = [request['reload']]
for ammo in request['reload']:
self.reload(self.campaign['ammo'][ammo])
except Exception as e:
log.error(f'Cannot extract {request["extract"]} from {resp}:\n{e}', exc_info=True)
if 'script' in request:
Expand Down Expand Up @@ -204,14 +195,19 @@ def worker(self, thread_id, ammo):
finally:
request_logging.main_thread()

def reload(self, requests, repeat=None, **kwargs):
def reload(self, requests, repeat=None, prepare=False, **kwargs):
"""
Add request(s) to the bombardier queue `repeat`-times (args.repeat if None).
If `repeat` field exists in the request additionally repeats as defined by it.
Requests can be one request or list of requests.
If supply specified it'll be used in addition to self.supply
If supply specified it'll be used in addition to self.supply.
Arg `prepare` indicate call from main, not from request script.
So we know if any scripts call reload (self.script_fired)
"""
if not prepare:
self.request_fired = True
if not isinstance(requests, list):
requests = [requests]
if repeat is None:
Expand Down
1 change: 0 additions & 1 deletion bombard/examples/simplest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ prepare:
url: "{host}posts" # use {host} from global supply
extract: # what to extract and place to global supply
id: "[0]['id']"
reload: getPost # schedule getPost request after this one
dry: [id: 1] # result of postsList if --dry mode
ammo:
getPost:
Expand Down
25 changes: 17 additions & 8 deletions bombard/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import os.path
from shutil import copyfile
from bombard.expand_file_name import get_campaign_file_name, show_folder, expand_relative_file_name
import sys
from bombard.terminal_colours import red, RED, OFF
import bombard


def guess_type(value: str):
Expand Down Expand Up @@ -88,17 +88,26 @@ def start_campaign(args, campaign_file_name):

bombardier = Bombardier(supply, args, campaign_book)
if 'prepare' in campaign_book:
requests = campaign_book['prepare']
repeat = 1
for ammo in campaign_book['prepare'].values():
bombardier.reload(ammo, repeat=1, prepare=True)
bombardier.start()
if not bombardier.request_fired: # no request fired anything so we have to fire ammo section by ourself
for ammo in campaign_book['ammo'].values():
bombardier.reload(ammo, repeat=args.repeat)
bombardier.bombard()
else:
requests = campaign_book['ammo']
repeat = args.repeat
for ammo in requests.values():
bombardier.reload(ammo, repeat=repeat)
bombardier.bombard()
for ammo in campaign_book['ammo'].values():
bombardier.reload(ammo, repeat=args.repeat)
bombardier.bombard()


def campaign(args):
if args.version:
print(bombard.__name__, bombard.version())
with open(os.path.join(os.path.dirname(bombard.__file__), 'LICENSE.txt'), 'r') as license:
print(license.readline())
return

if args.quiet:
level = logging.WARNING
elif args.verbose:
Expand Down
34 changes: 23 additions & 11 deletions docs/campaign.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,20 @@ Also you can use any custom indices you want like that
so ``name: ['name']`` is the same as ``name:``.

dry
___

If you run Bombard with ``--dry`` it do not make actual HTTP requests.
And if you have ``dry`` section in request Bombard will use it as
result of this ``dry`` request.

prepare
-------

If campaign file has this section, Bombard will fire only requests from
this section.

Each request will be repeated ``--repeat`` times as defined in command line
(or by default value for this option).
If campaign file has this section, Bombard will start fire with requests
from this section.

And scripts in this section are responsible to fire scripts from ``ammo``
section, like this
Requests in this section can fire requests from ``ammo`` section, like this:

.. code-block:: yaml
Expand All @@ -124,17 +127,26 @@ section, like this
As you see above you can send some variable not only to global ``supply``
but just to the request you fire.

If ``prepare`` section did not fire any ``ammo`` requests, Bombard after
``prepare`` will fire all requests from ``ammo`` section.

That is, if you have only ``extract`` sections in ``prepare`` requests.
Or if ``scripts`` in ``prepare`` requests do not call ``reload`` to fire
requests from ``ammo``. Then Bombard will fire all ``ammo`` requests
after ``prepare`` requests.

ammo
----

If campaign file do not have ``prepare`` section, Bombard will fire all requests
from this section.
If campaign file do not have ``prepare`` section, Bombard will just fire all
requests from this section.

Each request will be repeated ``--repeat`` times as defined in command line
(or by default value for this option).

Otherwise this requests should be fired from requests from ``prepare`` section
as described above.
Otherwise bombard will fire ``prepare`` section and after that if ``prepare``
requests did not fire any requests from ``ammo``, bombard will fire all
requests from ``ammo``.

Example of ``ammo`` request for the request that you see in ``prepare``
section:
Expand Down
10 changes: 2 additions & 8 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,9 @@
# built documents.
#
# The short X.Y version.
import sys
sys.path.insert(0, '../')
import bombard
import sys; sys.path.insert(0, '../'); import bombard
version = bombard.version()


version = '1.2'
# The full version, including alpha/beta/rc tags.
release = '1.2'
release = bombard.__version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[metadata]
# This includes the license file(s) in the wheel.
# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file
license_files = LICENSE.txt
license_files = bombard/LICENSE.txt
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
with open("README.rst", "r") as fh:
long_description = fh.read()

import sys
sys.path.insert(0, '../')
import bombard
import sys; sys.path.insert(0, '../'); import bombard

setuptools.setup(
name='bombard',
Expand Down
3 changes: 3 additions & 0 deletions tests/fake_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ class FakeArgs(AttrDict):
init = False
examples = False
file_name = CAMPAIGN_FILE_NAME
version = False
supply={}
repeat=1
33 changes: 33 additions & 0 deletions tests/fake_jsonplaceholder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json


FAKE_RESP = {
('GET', '/posts'): [
{'id': 1}, {'id': 2}, {'id': 3}
],
('GET', '/posts/1'): '',
('GET', '/posts/2'): '',
('GET', '/posts/3'): '',
}


class FakeResp:
status = 200
body = None

def __init__(self, resp_body):
self.body = resp_body

def read(self):
return self.body


class FakeJSONPlaceholder:
def request(self, method, path, body=None, headers=None):
self.method = method
self.path = path

def response(self):
return FakeResp(json.dumps(
FAKE_RESP[(self.method, self.path)]
))
28 changes: 28 additions & 0 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from bombard import http_request
from bombard.request_logging import setup_logging
import logging
from tests.stdout_capture import CaptureOutput
from tests.fake_args import FakeArgs
from bombard.main import campaign
from tests.fake_jsonplaceholder import FakeJSONPlaceholder


class TestExamples(unittest.TestCase):
def setUp(self):
self.fake_server = FakeJSONPlaceholder()
http_request.http.client.HTTPSConnection.request = self.fake_server.request
http_request.http.client.HTTPSConnection.getresponse = self.fake_server.response
setup_logging(logging.DEBUG)

def testSimplest(self):
with CaptureOutput(capture=True) as captured:
campaign(FakeArgs(example='simplest'))
self.assertIn(
'GET jsonplaceholder.typicode.com/posts',
captured.output
)
self.assertIn(
'GET jsonplaceholder.typicode.com/posts/1',
captured.output
)
21 changes: 21 additions & 0 deletions tests/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from tests.stdout_capture import CaptureOutput
import unittest
from bombard.main import campaign
from tests.fake_args import FakeArgs
import bombard


class TestVersion(unittest.TestCase):
def testVersion(self):
with CaptureOutput() as captured:
campaign(FakeArgs(version=True))

with open(f'bombard/LICENSE.txt') as license:
self.assertIn(
license.readline(),
captured.output
)
self.assertIn(
bombard.version(),
captured.output
)

0 comments on commit 61dfc7c

Please sign in to comment.