SlideShare a Scribd company logo
1 of 100
Download to read offline
Next Level Testing
Revisited
James Saryerwinnie
@jsaryer
PyGotham 2017
Next Level Testing
Revisited
James Saryerwinnie
@jsaryer
PyGotham 2017
Property based testing
* Overview
* State-based testing
Fuzz testing
* Overview
* Multi-argument fuzzing
Agenda
Property based testing
* Overview
* State-based testing
Fuzz testing
* Overview
* Multi-argument fuzzing
Agenda
abs.py
def test_abs_always_positive():
assert abs(1) == 1
def test_abs_negative():
assert abs(-1) == 1
def test_abs_zero_is_zero():
assert abs(0) == 0
Property example
def test_abs_always_positive():
assert abs(1) == 1
def test_abs_negative():
assert abs(-1) == 1
def test_abs_zero_is_zero():
assert abs(0) == 0
abs.pyProperty example
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
For all integers i, abs(i) should always
be greater than or equal to 0.
Can we do better?
Property Based Testing
1. Write assertions about the properties of a function
2. Generate random input data that violates these assertions
3. Minimize the example to be as simple as possible
Hypothesis
• Integrates with unittest/pytest
• Powerful test data generation
• Generates minimal test cases on failure
• pip install hypothesis
Strategies
>>> import hypothesis.strategies as s
>>> test_data = s.integers()
>>> test_data.example()
-214460024625629886
>>> test_data.example()
288486085571772
>>> test_data.example()
-2199980509
>>> test_data.example()
-207377623894
>>> test_data.example()
4588868
abs.py
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
Property example
from hypothesis import given
import hypothesis.strategies as s
@given(s.integers())
def test_abs(x):
assert abs(x) >= 0
abs.py
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
Property example
from hypothesis import given
import hypothesis.strategies as s
@given(s.integers())
def test_abs(x):
assert abs(x) >= 0
abs.py
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
Property example
from hypothesis import given
import hypothesis.strategies as s
@given(s.integers())
def test_abs(x):
assert abs(x) >= 0
abs.py
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
Property example
from hypothesis import given
import hypothesis.strategies as s
@given(s.integers())
def test_abs(x):
assert abs(x) >= 0
abs.py
import random
def test_abs():
for _ in range(1000):
n = random.randint(
-sys.maxint,
sys.maxint)
assert abs(n) >= 0
Property example
from hypothesis import given
import hypothesis.strategies as s
@given(s.integers())
def test_abs(x):
assert abs(x) >= 0
State based testing
• Instead of a single value, produce a set of steps
• At each step, some type of internal state is updated
• An failed test gives you a set of instructions to create the failure
Chalice
• Serverless microframework for python
• Create AWS Lambda Functions and Amazon API Gateway APIs
• Multiple deployment backends
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda Amazon CloudWatch Events
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Create index/cron
functions
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Create index/cron
functions
Create new function
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda Amazon CloudWatch Events
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Create index/cron
functions
T2 - Create hello_pygotham
function
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Lambda functions
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Create index/cron
functions
T2 - Create hello_pygotham
function
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
Lambda functions
AWS Lambda
AWS Lambda
T1 - Create index/cron
functions
T2 - Create hello_pygotham
function
T3 - Delete cron
function
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
Lambda functions
AWS Lambda
AWS Lambda
T1 - Create index/cron
functions
T2 - Create hello_pygotham
function
T3 - Delete cron
function
Deployment Properties
• Every new function in python code should correspond to a create API call
• Deleting a function in python code deletes the remote resource (unreferenced resources)
• Existing resources should have update calls (if needed)
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
unreferenced resource
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
T2 - Create hello_pygotham
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
T2 - Create hello_pygotham
app.py
from chalice import Chalice
app = Chalice(app_name='myproject')
@app.lambda_function()
def index(event, context):
return {'hello': 'world'}
@app.lambda_function()
def hello_pygotham(event, context):
return {'hello': 'pygotham'}
@app.schedule('rate(5 minutes)')
def cron(event):
return {}
Deployment properties
AWS Lambda
AWS Lambda
AWS Lambda Amazon CloudWatch Events
T1 - Delete hello_pygotham
T2 - Create hello_pygotham
Already exists!
filename.py
T1 - Create function named index
T2 - Create function named cron
T3 - Create function named hello_pygotham
T4 - Delete function named hello_pygotham
T5 - Create function named hello_pygotham (error)
Example input
filename.py
T1 - Create function named index
T2 - Create function named cron
T3 - Create function named hello_pygotham
T4 - Delete function named hello_pygotham
T5 - Create function named hello_pygotham (error)
Example input
Steps
}
filename.py
T1 - Create function named index
T2 - Create function named cron
T3 - Create function named hello_pygotham
T4 - Delete function named hello_pygotham
T5 - Create function named hello_pygotham (error)
Example input
Steps
}
How do we do this in code?
test_deployer.py
from hypothesis.stateful import GenericStateMachine
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
…
def steps(self):
…
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
…
def steps(self):
…
def execute_step(self, step):
…
State based testing
1. Subclass from GenericStateMachine
test_deployer.py
from hypothesis.stateful import GenericStateMachine
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
…
def steps(self):
…
def execute_step(self, step):
…
State based testing
1. Subclass from GenericStateMachine
2. Initialize starting state in __init__()
test_deployer.py
from hypothesis.stateful import GenericStateMachine
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
…
def steps(self):
…
def execute_step(self, step):
…
State based testing
1. Subclass from GenericStateMachine
2. Initialize starting state in __init__()
3. steps(), valid actions based on current state
test_deployer.py
from hypothesis.stateful import GenericStateMachine
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
…
def steps(self):
…
def execute_step(self, step):
…
State based testing
1. Subclass from GenericStateMachine
2. Initialize starting state in __init__()
3. steps(), valid actions based on current state
4. Execute randomly selected step
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
(self._add_function, 'random-name')
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
# type: List[LambdaFunction]
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
(self._delete_function, <LambdaFunction()>)
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def __init__(self, chalice_app=None):
self.chalice_app = chalice_app
def steps(self):
add_lambda_function = tuples(
just(self._add_function),
text(alphabet=string.ascii_lowercase)
)
if not self.chalice_app.lambda_functions:
return add_lambda_function
else:
delete_lambda_function = tuples(
just(self._delete_function),
sampled_from(self.chalice_app.lambda_functions)
)
return (add_lambda_function | delete_lambda_function)
def execute_step(self, step):
…
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
(self._add_function, 'random-name')
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
def execute_step(self, step):
action, arg = step
action(arg)
# After updating our state we can go ahead and go through our
# deploy process.
self._mock_deploy()
def _add_function(self, name):
function = LambdaFunction(func=lambda x, y: {}, name=name,
handler_string='app.%s' % name)
self.chalice_app.lambda_functions.append(function)
def _delete_function(self, function):
lambda_functions = self.chalice_app.lambda_functions
lambda_functions.remove(function)
State based testing
test_deployer.py
from hypothesis.stateful import GenericStateMachine
from hypothesis.strategies import tuples, sampled_from, just, text
class DeployerTracker(GenericStateMachine):
# Core business logic
# a) Do a real deploy to AWS, query for real services (integration test)
# b) Use mock/fake components and verify properties (unit tests)
def _mock_deploy(self):
…
State based testing
Summary
• State based tests assert properties after executing a sequence of steps
• Used as both unit and integration tests
• Verify correct state of the world after deploying multiple versions a chalice app
Property based testing
* Overview
* State-based testing
Fuzz testing
* Overview
* Multi-argument fuzzing
Agenda
fuzzing.py
while True:
fuzzing_input = generate_random_input()
try:
code_under_test(fuzzing_input)
except AllowedExceptions:
pass
except:
# An unexpected exception was raised.
report_fuzzing_failure(fuzzing_input)
Simplified fuzzing
AFL - American Fuzzy Lop
• Coverage guided genetic fuzzer
• Fast
• Simple to use
python-afl
• Create a python program that reads input from stdin
• Raise an exception on error cases (“crashes”)
• Create a set of sample input files, one sample input per file
bug.py
def test(x):
# Invoke functions/classes to
# test given random input of ‘x’.
…
def main():
test(sys.stdin.read())
import afl
while afl.loop():
main()
AFL Fuzzing Example
bug.py
def test(x):
# Invoke functions/classes to
# test given random input of ‘x’.
…
def main():
test(sys.stdin.read())
import afl
while afl.loop():
main()
AFL Fuzzing Example
bug.py
def test(x):
# Invoke functions/classes to
# test given random input of ‘x’.
…
def main():
test(sys.stdin.read())
import afl
while afl.loop():
main()
AFL Fuzzing Example
bug.py
def test(x):
# Invoke functions/classes to
# test given random input of ‘x’.
…
def main():
test(sys.stdin.read())
import afl
while afl.loop():
main()
AFL Fuzzing Example
Test code
AFL fuzz integration
1. Each iteration, new input is generated on stdin
2. Read from stdin and pass the new input
to the test function
$ cat corpus/a
a
$ py-afl-fuzz -o results/ -i corpus/ -- $(which python) bug.py
Running python-afl
Sample inputCrashes
$ tree results/
results/
├── crashes
│   ├── id:000000,sig:10,src:000005,op:arith8,pos:4,val:+23
│   └── README.txt
├── fuzz_bitmap
├── fuzzer_stats
├── hangs
├── plot_data
└── queue
├── id:000000,orig:a
├── id:000001,src:000000,op:havoc,rep:16,+cov
├── id:000002,src:000001,op:havoc,rep:16,+cov
├── id:000003,src:000002,op:arith8,pos:1,val:+19,+cov
├── id:000004,src:000003,op:arith8,pos:2,val:+5,+cov
└── id:000005,src:000004,op:arith8,pos:3,val:+5,+cov
AFL Fuzzing Example
$ tree results/
results/
├── crashes
│   ├── id:000000,sig:10,src:000005,op:arith8,pos:4,val:+23
│   └── README.txt
├── fuzz_bitmap
├── fuzzer_stats
├── hangs
├── plot_data
└── queue
├── id:000000,orig:a
├── id:000001,src:000000,op:havoc,rep:16,+cov
├── id:000002,src:000001,op:havoc,rep:16,+cov
├── id:000003,src:000002,op:arith8,pos:1,val:+19,+cov
├── id:000004,src:000003,op:arith8,pos:2,val:+5,+cov
└── id:000005,src:000004,op:arith8,pos:3,val:+5,+cov
AFL Fuzzing Example
Value that caused the crash
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,False,False
True,False,False
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,False,False False,True,False
False,True,False
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,False,False False,True,False False,False,True Total
False,False,True
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,False,False False,True,False False,False,True Total
100% line coverage
100% branch coverage
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,True,True
Each branch is taken in a single invocation
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,True,True
Each branch is taken in a single invocation
4096 ^ 3 = 68719476736 elements
68719476736 * 8 bytes/element = 512 GB
def code_path(a, b, c):
power = 0
if a:
power += 1
if b:
power += 1
if c:
power += 1
return [0] * (4096 ** power)
Path coverage
True,True,True
Each branch is taken in a single invocation
4096 ^ 3 = 68719476736 elements
68719476736 * 8 bytes/element = 512 GB
A Query Language for JSON
import jmespath
# .search(expression, input_data)
jmespath.search(‘a.b', {'a': {'b': {'c': 'd'}}}) # {'c': 'd'}
$ aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId, State.Name]'
- name: "Display all cluster names"
debug: var=item
with_items: "{{domain_definition|json_query('domain.cluster[*].name')}}"
AWS CLI
Ansible
Python API
>>> import jmespath
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath search() usage
>>> import jmespath
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath search() usage
>>> import jmespath
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath search() usage
>>> import jmespath
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
{'c': {'d': [0, 1, 2]}}
JMESPath search() usage
>>> import jmespath
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
{'c': {'d': [0, 1, 2]}}
>>> jmespath.search('a.b.c.d', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
[0, 1, 2]
>>> jmespath.search('a.b.c.d[0]', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
0
JMESPath search() usage
>>> jmespath.compile('a.b.c.d[0]')
JMESPath compilation
>>> jmespath.compile('a.b.c.d[0]')
JMESPath compilation
Generated by AFL fuzz
(arbitrary byte sequence)
>>> jmespath.compile(‘a.b.c.d[0]')
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath compilation
>>> jmespath.compile(‘a.b.c.d[0]')
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath compilation
How do we generate multiple arguments with different types?
Multiple Input Parameters
• Random data is provided through sys.stdin
• Coverage based mutations
• Starting corpus of input comes from a set of files
• Create a pseudo-file format representing arguments
• afl-fuzz magically figures out what we mean?
>>> # jmespath.search(expression: str, data: Any)
>>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}})
JMESPath compilation
input.txt
a.b
----DELIMITER----
{"a": {"b": {"c": {"d": [0, 1, 2]}}}}
Starting Corpus
`2.0ý`
----DELIMITER----
{"": null, "uf1bcnu001du91feu0010uff1fn000u0207u55bfu0156r!": ""}
input.txt
foo.bar
----DELIMITER----
{"": null, "a": [1,2,3]}
input.txt
foo.bar[0].baz
----DELIMITER----
{"b": true, "a": [1,2,3]}
input.txt
.
.
.
fuzz.py
import jmespath
from jmespath import exceptions
def main():
try:
newval = sys.stdin.read()
expression, partition, data = newval.partition('n----DELIMITER----n')
if not expression or not data:
return
if not partition:
return
try:
parsed = json.loads(data)
except Exception as e:
return
result = jmespath.search(expression, parsed)
except exceptions.JMESPathError as e:
# JMESPathError is allowed.
return 0
return 0
import afl
while afl.loop():
main()
Fuzzing
fuzz.py
import jmespath
from jmespath import exceptions
def main():
try:
newval = sys.stdin.read()
expression, partition, data = newval.partition('n----DELIMITER----n')
if not expression or not data:
return
if not partition:
return
try:
parsed = json.loads(data)
except Exception as e:
return
result = jmespath.search(expression, parsed)
except exceptions.JMESPathError as e:
# JMESPathError is allowed.
return 0
return 0
import afl
while afl.loop():
main()
Fuzzing
input.txt
a.b
----DELIMITER----
{"a": {"b": {"c": {"d": [0, 1, 2]}}}}
fuzz.py
import jmespath
from jmespath import exceptions
def main():
try:
newval = sys.stdin.read()
expression, partition, data = newval.partition('n----DELIMITER----n')
if not expression or not data:
return
if not partition:
return
try:
parsed = json.loads(data)
except Exception as e:
return
result = jmespath.search(expression, parsed)
except exceptions.JMESPathError as e:
# JMESPathError is allowed.
return 0
return 0
import afl
while afl.loop():
main()
Fuzzing
input.txt
a.b
----DELIMITER----
{"a": {"b": {"c": {"d": [0, 1, 2]}}}}
fuzz.py
import jmespath
from jmespath import exceptions
def main():
try:
newval = sys.stdin.read()
expression, partition, data = newval.partition('n----DELIMITER----n')
if not expression or not data:
return
if not partition:
return
try:
parsed = json.loads(data)
except Exception as e:
return
result = jmespath.search(expression, parsed)
except exceptions.JMESPathError as e:
# JMESPathError is allowed.
return 0
return 0
import afl
while afl.loop():
main()
Fuzzing
input.txt
a.b
----DELIMITER----
{"a": {"b": {"c": {"d": [0, 1, 2]}}}}
fuzz.py
import jmespath
from jmespath import exceptions
def main():
try:
newval = sys.stdin.read()
expression, partition, data = newval.partition('n----DELIMITER----n')
if not expression or not data:
return
if not partition:
return
try:
parsed = json.loads(data)
except Exception as e:
return
result = jmespath.search(expression, parsed)
except exceptions.JMESPathError as e:
# JMESPathError is allowed.
return 0
return 0
import afl
while afl.loop():
main()
Fuzzing
input.txt
a.b
----DELIMITER----
{"a": {"b": {"c": {"d": [0, 1, 2]}}}}
bad-delimiter - 0 ( 0.00%)
bad-json - 23398576 (47.30%)
jp-error - 6575956 (13.29%)
no-errors - 337233 ( 0.68%)
Does it work?
Summary
• python-afl intelligently figures out meaningful input data
• Treat multi-param input as pseudo file format
• Possible to use property-based testing approach
Property based testing
* Overview
* State-based testing
Fuzz testing
* Overview
* Multi-argument fuzzing
Agenda
Thanks!
• American Fuzzy Lop - http://lcamtuf.coredump.cx/afl/
• Hypothesis - http://hypothesis.works/
• JMESPath - http://jmespath.org/
• Chalice - http://github.com/aws/chalice
• Next Level Testing (PyCon 2017) - https://www.youtube.com/watch?v=jmsk1QZQEvQ
• @jsaryer

More Related Content

What's hot

Swift Ready for Production?
Swift Ready for Production?Swift Ready for Production?
Swift Ready for Production?Crispy Mountain
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalQA or the Highway
 
Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014hezamu
 
Compiled Python UDFs for Impala
Compiled Python UDFs for ImpalaCompiled Python UDFs for Impala
Compiled Python UDFs for ImpalaCloudera, Inc.
 
InterConnect: Server Side Swift for Java Developers
InterConnect:  Server Side Swift for Java DevelopersInterConnect:  Server Side Swift for Java Developers
InterConnect: Server Side Swift for Java DevelopersChris Bailey
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate BustersHamletDRC
 
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)Uri Laserson
 
AOP in Python API design
AOP in Python API designAOP in Python API design
AOP in Python API designmeij200
 
Functional Programming with JavaScript
Functional Programming with JavaScriptFunctional Programming with JavaScript
Functional Programming with JavaScriptMark Shelton
 
3 things you must know to think reactive - Geecon Kraków 2015
3 things you must know to think reactive - Geecon Kraków 20153 things you must know to think reactive - Geecon Kraków 2015
3 things you must know to think reactive - Geecon Kraków 2015Manuel Bernhardt
 
Flying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnightFlying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnightWiem Zine Elabidine
 
JavaScript for ABAP Programmers - 5/7 Functions
JavaScript for ABAP Programmers - 5/7 FunctionsJavaScript for ABAP Programmers - 5/7 Functions
JavaScript for ABAP Programmers - 5/7 FunctionsChris Whealy
 
20170529 jaws beginnerxcli_monitoringxcli_datadog
20170529 jaws beginnerxcli_monitoringxcli_datadog20170529 jaws beginnerxcli_monitoringxcli_datadog
20170529 jaws beginnerxcli_monitoringxcli_datadogMasahiro Hattori
 
Back to the futures, actors and pipes: using Akka for large-scale data migration
Back to the futures, actors and pipes: using Akka for large-scale data migrationBack to the futures, actors and pipes: using Akka for large-scale data migration
Back to the futures, actors and pipes: using Akka for large-scale data migrationManuel Bernhardt
 
Akka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutesAkka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutesKonrad Malawski
 
Evaluating Hype in scala
Evaluating Hype in scalaEvaluating Hype in scala
Evaluating Hype in scalaPere Villega
 

What's hot (20)

Swift Ready for Production?
Swift Ready for Production?Swift Ready for Production?
Swift Ready for Production?
 
F(3)
F(3)F(3)
F(3)
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert Fornal
 
Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014
 
Typescript barcelona
Typescript barcelonaTypescript barcelona
Typescript barcelona
 
Partial Functions in Scala
Partial Functions in ScalaPartial Functions in Scala
Partial Functions in Scala
 
Compiled Python UDFs for Impala
Compiled Python UDFs for ImpalaCompiled Python UDFs for Impala
Compiled Python UDFs for Impala
 
InterConnect: Server Side Swift for Java Developers
InterConnect:  Server Side Swift for Java DevelopersInterConnect:  Server Side Swift for Java Developers
InterConnect: Server Side Swift for Java Developers
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate Busters
 
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)
Numba-compiled Python UDFs for Impala (Impala Meetup 5/20/14)
 
AOP in Python API design
AOP in Python API designAOP in Python API design
AOP in Python API design
 
Functional Programming with JavaScript
Functional Programming with JavaScriptFunctional Programming with JavaScript
Functional Programming with JavaScript
 
3 things you must know to think reactive - Geecon Kraków 2015
3 things you must know to think reactive - Geecon Kraków 20153 things you must know to think reactive - Geecon Kraków 2015
3 things you must know to think reactive - Geecon Kraków 2015
 
Flying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnightFlying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnight
 
JavaScript for ABAP Programmers - 5/7 Functions
JavaScript for ABAP Programmers - 5/7 FunctionsJavaScript for ABAP Programmers - 5/7 Functions
JavaScript for ABAP Programmers - 5/7 Functions
 
20170529 jaws beginnerxcli_monitoringxcli_datadog
20170529 jaws beginnerxcli_monitoringxcli_datadog20170529 jaws beginnerxcli_monitoringxcli_datadog
20170529 jaws beginnerxcli_monitoringxcli_datadog
 
Back to the futures, actors and pipes: using Akka for large-scale data migration
Back to the futures, actors and pipes: using Akka for large-scale data migrationBack to the futures, actors and pipes: using Akka for large-scale data migration
Back to the futures, actors and pipes: using Akka for large-scale data migration
 
Akka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutesAkka persistence == event sourcing in 30 minutes
Akka persistence == event sourcing in 30 minutes
 
Pure Future
Pure FuturePure Future
Pure Future
 
Evaluating Hype in scala
Evaluating Hype in scalaEvaluating Hype in scala
Evaluating Hype in scala
 

Similar to Next Level Testing Revisited

Flask patterns
Flask patternsFlask patterns
Flask patternsit-people
 
Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Katy Slemon
 
Writing Redis in Python with asyncio
Writing Redis in Python with asyncioWriting Redis in Python with asyncio
Writing Redis in Python with asyncioJames Saryerwinnie
 
Alex Casalboni - Configuration management and service discovery - Codemotion ...
Alex Casalboni - Configuration management and service discovery - Codemotion ...Alex Casalboni - Configuration management and service discovery - Codemotion ...
Alex Casalboni - Configuration management and service discovery - Codemotion ...Codemotion
 
【AWS Developers Meetup】RESTful APIをChaliceで紐解く
【AWS Developers Meetup】RESTful APIをChaliceで紐解く【AWS Developers Meetup】RESTful APIをChaliceで紐解く
【AWS Developers Meetup】RESTful APIをChaliceで紐解くAmazon Web Services Japan
 
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...DevOps_Fest
 
Testing My Patience
Testing My PatienceTesting My Patience
Testing My PatienceAdam Lowry
 
Building Serverless Applications with AWS Chalice
Building Serverless Applications with AWS ChaliceBuilding Serverless Applications with AWS Chalice
Building Serverless Applications with AWS ChaliceAmazon Web Services
 
JavaScript (without DOM)
JavaScript (without DOM)JavaScript (without DOM)
JavaScript (without DOM)Piyush Katariya
 
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)Amazon Web Services
 
Python Unit Test
Python Unit TestPython Unit Test
Python Unit TestDavid Xie
 
Serverless archtiectures
Serverless archtiecturesServerless archtiectures
Serverless archtiecturesIegor Fadieiev
 
Qualidade levada a sério em Python - Emilio Simoni
Qualidade levada a sério em Python - Emilio SimoniQualidade levada a sério em Python - Emilio Simoni
Qualidade levada a sério em Python - Emilio SimoniGrupo de Testes Carioca
 
The Ring programming language version 1.3 book - Part 30 of 88
The Ring programming language version 1.3 book - Part 30 of 88The Ring programming language version 1.3 book - Part 30 of 88
The Ring programming language version 1.3 book - Part 30 of 88Mahmoud Samir Fayed
 
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptx
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptxTrack 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptx
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptxAmazon Web Services
 
Message-based communication patterns in distributed Akka applications
Message-based communication patterns in distributed Akka applicationsMessage-based communication patterns in distributed Akka applications
Message-based communication patterns in distributed Akka applicationsAndrii Lashchenko
 
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜崇之 清水
 

Similar to Next Level Testing Revisited (20)

Flask patterns
Flask patternsFlask patterns
Flask patterns
 
Practical Celery
Practical CeleryPractical Celery
Practical Celery
 
Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3
 
Writing Redis in Python with asyncio
Writing Redis in Python with asyncioWriting Redis in Python with asyncio
Writing Redis in Python with asyncio
 
Alex Casalboni - Configuration management and service discovery - Codemotion ...
Alex Casalboni - Configuration management and service discovery - Codemotion ...Alex Casalboni - Configuration management and service discovery - Codemotion ...
Alex Casalboni - Configuration management and service discovery - Codemotion ...
 
【AWS Developers Meetup】RESTful APIをChaliceで紐解く
【AWS Developers Meetup】RESTful APIをChaliceで紐解く【AWS Developers Meetup】RESTful APIをChaliceで紐解く
【AWS Developers Meetup】RESTful APIをChaliceで紐解く
 
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...
DevOps Fest 2019. Alex Casalboni. Configuration management and service discov...
 
Testing My Patience
Testing My PatienceTesting My Patience
Testing My Patience
 
Building Serverless Applications with AWS Chalice
Building Serverless Applications with AWS ChaliceBuilding Serverless Applications with AWS Chalice
Building Serverless Applications with AWS Chalice
 
JavaScript (without DOM)
JavaScript (without DOM)JavaScript (without DOM)
JavaScript (without DOM)
 
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)
AWS re:Invent 2016: Chalice: A Serverless Microframework for Python (DEV308)
 
Python Unit Test
Python Unit TestPython Unit Test
Python Unit Test
 
Serverless archtiectures
Serverless archtiecturesServerless archtiectures
Serverless archtiectures
 
Qualidade levada a sério em Python - Emilio Simoni
Qualidade levada a sério em Python - Emilio SimoniQualidade levada a sério em Python - Emilio Simoni
Qualidade levada a sério em Python - Emilio Simoni
 
The Ring programming language version 1.3 book - Part 30 of 88
The Ring programming language version 1.3 book - Part 30 of 88The Ring programming language version 1.3 book - Part 30 of 88
The Ring programming language version 1.3 book - Part 30 of 88
 
Unit test
Unit testUnit test
Unit test
 
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptx
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptxTrack 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptx
Track 4 Session 2_MAD03 容器技術和 AWS Lambda 讓您專注「應用優先」.pptx
 
25-functions.ppt
25-functions.ppt25-functions.ppt
25-functions.ppt
 
Message-based communication patterns in distributed Akka applications
Message-based communication patterns in distributed Akka applicationsMessage-based communication patterns in distributed Akka applications
Message-based communication patterns in distributed Akka applications
 
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜
RESTful API を Chalice で紐解く 〜 Python Serverless Microframework for AWS 〜
 

Recently uploaded

IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 

Recently uploaded (20)

IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 

Next Level Testing Revisited

  • 1. Next Level Testing Revisited James Saryerwinnie @jsaryer PyGotham 2017
  • 2. Next Level Testing Revisited James Saryerwinnie @jsaryer PyGotham 2017
  • 3. Property based testing * Overview * State-based testing Fuzz testing * Overview * Multi-argument fuzzing Agenda
  • 4. Property based testing * Overview * State-based testing Fuzz testing * Overview * Multi-argument fuzzing Agenda
  • 5. abs.py def test_abs_always_positive(): assert abs(1) == 1 def test_abs_negative(): assert abs(-1) == 1 def test_abs_zero_is_zero(): assert abs(0) == 0 Property example
  • 6. def test_abs_always_positive(): assert abs(1) == 1 def test_abs_negative(): assert abs(-1) == 1 def test_abs_zero_is_zero(): assert abs(0) == 0 abs.pyProperty example import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 For all integers i, abs(i) should always be greater than or equal to 0. Can we do better?
  • 7. Property Based Testing 1. Write assertions about the properties of a function 2. Generate random input data that violates these assertions 3. Minimize the example to be as simple as possible
  • 8. Hypothesis • Integrates with unittest/pytest • Powerful test data generation • Generates minimal test cases on failure • pip install hypothesis
  • 9. Strategies >>> import hypothesis.strategies as s >>> test_data = s.integers() >>> test_data.example() -214460024625629886 >>> test_data.example() 288486085571772 >>> test_data.example() -2199980509 >>> test_data.example() -207377623894 >>> test_data.example() 4588868
  • 10. abs.py import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 Property example from hypothesis import given import hypothesis.strategies as s @given(s.integers()) def test_abs(x): assert abs(x) >= 0
  • 11. abs.py import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 Property example from hypothesis import given import hypothesis.strategies as s @given(s.integers()) def test_abs(x): assert abs(x) >= 0
  • 12. abs.py import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 Property example from hypothesis import given import hypothesis.strategies as s @given(s.integers()) def test_abs(x): assert abs(x) >= 0
  • 13. abs.py import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 Property example from hypothesis import given import hypothesis.strategies as s @given(s.integers()) def test_abs(x): assert abs(x) >= 0
  • 14. abs.py import random def test_abs(): for _ in range(1000): n = random.randint( -sys.maxint, sys.maxint) assert abs(n) >= 0 Property example from hypothesis import given import hypothesis.strategies as s @given(s.integers()) def test_abs(x): assert abs(x) >= 0
  • 15. State based testing • Instead of a single value, produce a set of steps • At each step, some type of internal state is updated • An failed test gives you a set of instructions to create the failure
  • 16. Chalice • Serverless microframework for python • Create AWS Lambda Functions and Amazon API Gateway APIs • Multiple deployment backends
  • 17. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions
  • 18. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda Amazon CloudWatch Events
  • 19. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Create index/cron functions
  • 20. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Create index/cron functions Create new function
  • 21. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda Amazon CloudWatch Events
  • 22. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events
  • 23. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Create index/cron functions T2 - Create hello_pygotham function
  • 24. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Lambda functions AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Create index/cron functions T2 - Create hello_pygotham function
  • 25. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} Lambda functions AWS Lambda AWS Lambda T1 - Create index/cron functions T2 - Create hello_pygotham function T3 - Delete cron function
  • 26. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} Lambda functions AWS Lambda AWS Lambda T1 - Create index/cron functions T2 - Create hello_pygotham function T3 - Delete cron function
  • 27. Deployment Properties • Every new function in python code should correspond to a create API call • Deleting a function in python code deletes the remote resource (unreferenced resources) • Existing resources should have update calls (if needed)
  • 28. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events
  • 29. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham
  • 30. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham
  • 31. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham unreferenced resource
  • 32. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham T2 - Create hello_pygotham
  • 33. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham T2 - Create hello_pygotham
  • 34. app.py from chalice import Chalice app = Chalice(app_name='myproject') @app.lambda_function() def index(event, context): return {'hello': 'world'} @app.lambda_function() def hello_pygotham(event, context): return {'hello': 'pygotham'} @app.schedule('rate(5 minutes)') def cron(event): return {} Deployment properties AWS Lambda AWS Lambda AWS Lambda Amazon CloudWatch Events T1 - Delete hello_pygotham T2 - Create hello_pygotham Already exists!
  • 35. filename.py T1 - Create function named index T2 - Create function named cron T3 - Create function named hello_pygotham T4 - Delete function named hello_pygotham T5 - Create function named hello_pygotham (error) Example input
  • 36. filename.py T1 - Create function named index T2 - Create function named cron T3 - Create function named hello_pygotham T4 - Delete function named hello_pygotham T5 - Create function named hello_pygotham (error) Example input Steps }
  • 37. filename.py T1 - Create function named index T2 - Create function named cron T3 - Create function named hello_pygotham T4 - Delete function named hello_pygotham T5 - Create function named hello_pygotham (error) Example input Steps } How do we do this in code?
  • 38. test_deployer.py from hypothesis.stateful import GenericStateMachine class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): … def steps(self): … def execute_step(self, step): … State based testing
  • 39. test_deployer.py from hypothesis.stateful import GenericStateMachine class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): … def steps(self): … def execute_step(self, step): … State based testing 1. Subclass from GenericStateMachine
  • 40. test_deployer.py from hypothesis.stateful import GenericStateMachine class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): … def steps(self): … def execute_step(self, step): … State based testing 1. Subclass from GenericStateMachine 2. Initialize starting state in __init__()
  • 41. test_deployer.py from hypothesis.stateful import GenericStateMachine class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): … def steps(self): … def execute_step(self, step): … State based testing 1. Subclass from GenericStateMachine 2. Initialize starting state in __init__() 3. steps(), valid actions based on current state
  • 42. test_deployer.py from hypothesis.stateful import GenericStateMachine class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): … def steps(self): … def execute_step(self, step): … State based testing 1. Subclass from GenericStateMachine 2. Initialize starting state in __init__() 3. steps(), valid actions based on current state 4. Execute randomly selected step
  • 43. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 44. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 45. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 46. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing (self._add_function, 'random-name')
  • 47. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing # type: List[LambdaFunction]
  • 48. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 49. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing (self._delete_function, <LambdaFunction()>)
  • 50. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 51. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def __init__(self, chalice_app=None): self.chalice_app = chalice_app def steps(self): add_lambda_function = tuples( just(self._add_function), text(alphabet=string.ascii_lowercase) ) if not self.chalice_app.lambda_functions: return add_lambda_function else: delete_lambda_function = tuples( just(self._delete_function), sampled_from(self.chalice_app.lambda_functions) ) return (add_lambda_function | delete_lambda_function) def execute_step(self, step): … State based testing
  • 52. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing
  • 53. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing
  • 54. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing (self._add_function, 'random-name')
  • 55. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing
  • 56. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing
  • 57. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): def execute_step(self, step): action, arg = step action(arg) # After updating our state we can go ahead and go through our # deploy process. self._mock_deploy() def _add_function(self, name): function = LambdaFunction(func=lambda x, y: {}, name=name, handler_string='app.%s' % name) self.chalice_app.lambda_functions.append(function) def _delete_function(self, function): lambda_functions = self.chalice_app.lambda_functions lambda_functions.remove(function) State based testing
  • 58. test_deployer.py from hypothesis.stateful import GenericStateMachine from hypothesis.strategies import tuples, sampled_from, just, text class DeployerTracker(GenericStateMachine): # Core business logic # a) Do a real deploy to AWS, query for real services (integration test) # b) Use mock/fake components and verify properties (unit tests) def _mock_deploy(self): … State based testing
  • 59. Summary • State based tests assert properties after executing a sequence of steps • Used as both unit and integration tests • Verify correct state of the world after deploying multiple versions a chalice app
  • 60. Property based testing * Overview * State-based testing Fuzz testing * Overview * Multi-argument fuzzing Agenda
  • 61. fuzzing.py while True: fuzzing_input = generate_random_input() try: code_under_test(fuzzing_input) except AllowedExceptions: pass except: # An unexpected exception was raised. report_fuzzing_failure(fuzzing_input) Simplified fuzzing
  • 62. AFL - American Fuzzy Lop • Coverage guided genetic fuzzer • Fast • Simple to use
  • 63. python-afl • Create a python program that reads input from stdin • Raise an exception on error cases (“crashes”) • Create a set of sample input files, one sample input per file
  • 64. bug.py def test(x): # Invoke functions/classes to # test given random input of ‘x’. … def main(): test(sys.stdin.read()) import afl while afl.loop(): main() AFL Fuzzing Example
  • 65. bug.py def test(x): # Invoke functions/classes to # test given random input of ‘x’. … def main(): test(sys.stdin.read()) import afl while afl.loop(): main() AFL Fuzzing Example
  • 66. bug.py def test(x): # Invoke functions/classes to # test given random input of ‘x’. … def main(): test(sys.stdin.read()) import afl while afl.loop(): main() AFL Fuzzing Example
  • 67. bug.py def test(x): # Invoke functions/classes to # test given random input of ‘x’. … def main(): test(sys.stdin.read()) import afl while afl.loop(): main() AFL Fuzzing Example Test code AFL fuzz integration 1. Each iteration, new input is generated on stdin 2. Read from stdin and pass the new input to the test function
  • 68. $ cat corpus/a a $ py-afl-fuzz -o results/ -i corpus/ -- $(which python) bug.py Running python-afl Sample inputCrashes
  • 69. $ tree results/ results/ ├── crashes │   ├── id:000000,sig:10,src:000005,op:arith8,pos:4,val:+23 │   └── README.txt ├── fuzz_bitmap ├── fuzzer_stats ├── hangs ├── plot_data └── queue ├── id:000000,orig:a ├── id:000001,src:000000,op:havoc,rep:16,+cov ├── id:000002,src:000001,op:havoc,rep:16,+cov ├── id:000003,src:000002,op:arith8,pos:1,val:+19,+cov ├── id:000004,src:000003,op:arith8,pos:2,val:+5,+cov └── id:000005,src:000004,op:arith8,pos:3,val:+5,+cov AFL Fuzzing Example
  • 70. $ tree results/ results/ ├── crashes │   ├── id:000000,sig:10,src:000005,op:arith8,pos:4,val:+23 │   └── README.txt ├── fuzz_bitmap ├── fuzzer_stats ├── hangs ├── plot_data └── queue ├── id:000000,orig:a ├── id:000001,src:000000,op:havoc,rep:16,+cov ├── id:000002,src:000001,op:havoc,rep:16,+cov ├── id:000003,src:000002,op:arith8,pos:1,val:+19,+cov ├── id:000004,src:000003,op:arith8,pos:2,val:+5,+cov └── id:000005,src:000004,op:arith8,pos:3,val:+5,+cov AFL Fuzzing Example Value that caused the crash
  • 71. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage
  • 72. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,False,False True,False,False
  • 73. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,False,False False,True,False False,True,False
  • 74. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,False,False False,True,False False,False,True Total False,False,True
  • 75. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,False,False False,True,False False,False,True Total 100% line coverage 100% branch coverage
  • 76. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,True,True Each branch is taken in a single invocation
  • 77. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,True,True Each branch is taken in a single invocation 4096 ^ 3 = 68719476736 elements 68719476736 * 8 bytes/element = 512 GB
  • 78. def code_path(a, b, c): power = 0 if a: power += 1 if b: power += 1 if c: power += 1 return [0] * (4096 ** power) Path coverage True,True,True Each branch is taken in a single invocation 4096 ^ 3 = 68719476736 elements 68719476736 * 8 bytes/element = 512 GB
  • 79. A Query Language for JSON import jmespath # .search(expression, input_data) jmespath.search(‘a.b', {'a': {'b': {'c': 'd'}}}) # {'c': 'd'} $ aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId, State.Name]' - name: "Display all cluster names" debug: var=item with_items: "{{domain_definition|json_query('domain.cluster[*].name')}}" AWS CLI Ansible Python API
  • 80. >>> import jmespath >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath search() usage
  • 81. >>> import jmespath >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath search() usage
  • 82. >>> import jmespath >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath search() usage
  • 83. >>> import jmespath >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) {'c': {'d': [0, 1, 2]}} JMESPath search() usage
  • 84. >>> import jmespath >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) {'c': {'d': [0, 1, 2]}} >>> jmespath.search('a.b.c.d', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) [0, 1, 2] >>> jmespath.search('a.b.c.d[0]', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) 0 JMESPath search() usage
  • 87. >>> jmespath.compile(‘a.b.c.d[0]') >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath compilation
  • 88. >>> jmespath.compile(‘a.b.c.d[0]') >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath compilation How do we generate multiple arguments with different types?
  • 89. Multiple Input Parameters • Random data is provided through sys.stdin • Coverage based mutations • Starting corpus of input comes from a set of files • Create a pseudo-file format representing arguments • afl-fuzz magically figures out what we mean?
  • 90. >>> # jmespath.search(expression: str, data: Any) >>> jmespath.search('a.b', {'a': {'b': {'c': {'d': [0, 1, 2]}}}}) JMESPath compilation input.txt a.b ----DELIMITER---- {"a": {"b": {"c": {"d": [0, 1, 2]}}}}
  • 91. Starting Corpus `2.0ý` ----DELIMITER---- {"": null, "uf1bcnu001du91feu0010uff1fn000u0207u55bfu0156r!": ""} input.txt foo.bar ----DELIMITER---- {"": null, "a": [1,2,3]} input.txt foo.bar[0].baz ----DELIMITER---- {"b": true, "a": [1,2,3]} input.txt . . .
  • 92. fuzz.py import jmespath from jmespath import exceptions def main(): try: newval = sys.stdin.read() expression, partition, data = newval.partition('n----DELIMITER----n') if not expression or not data: return if not partition: return try: parsed = json.loads(data) except Exception as e: return result = jmespath.search(expression, parsed) except exceptions.JMESPathError as e: # JMESPathError is allowed. return 0 return 0 import afl while afl.loop(): main() Fuzzing
  • 93. fuzz.py import jmespath from jmespath import exceptions def main(): try: newval = sys.stdin.read() expression, partition, data = newval.partition('n----DELIMITER----n') if not expression or not data: return if not partition: return try: parsed = json.loads(data) except Exception as e: return result = jmespath.search(expression, parsed) except exceptions.JMESPathError as e: # JMESPathError is allowed. return 0 return 0 import afl while afl.loop(): main() Fuzzing input.txt a.b ----DELIMITER---- {"a": {"b": {"c": {"d": [0, 1, 2]}}}}
  • 94. fuzz.py import jmespath from jmespath import exceptions def main(): try: newval = sys.stdin.read() expression, partition, data = newval.partition('n----DELIMITER----n') if not expression or not data: return if not partition: return try: parsed = json.loads(data) except Exception as e: return result = jmespath.search(expression, parsed) except exceptions.JMESPathError as e: # JMESPathError is allowed. return 0 return 0 import afl while afl.loop(): main() Fuzzing input.txt a.b ----DELIMITER---- {"a": {"b": {"c": {"d": [0, 1, 2]}}}}
  • 95. fuzz.py import jmespath from jmespath import exceptions def main(): try: newval = sys.stdin.read() expression, partition, data = newval.partition('n----DELIMITER----n') if not expression or not data: return if not partition: return try: parsed = json.loads(data) except Exception as e: return result = jmespath.search(expression, parsed) except exceptions.JMESPathError as e: # JMESPathError is allowed. return 0 return 0 import afl while afl.loop(): main() Fuzzing input.txt a.b ----DELIMITER---- {"a": {"b": {"c": {"d": [0, 1, 2]}}}}
  • 96. fuzz.py import jmespath from jmespath import exceptions def main(): try: newval = sys.stdin.read() expression, partition, data = newval.partition('n----DELIMITER----n') if not expression or not data: return if not partition: return try: parsed = json.loads(data) except Exception as e: return result = jmespath.search(expression, parsed) except exceptions.JMESPathError as e: # JMESPathError is allowed. return 0 return 0 import afl while afl.loop(): main() Fuzzing input.txt a.b ----DELIMITER---- {"a": {"b": {"c": {"d": [0, 1, 2]}}}}
  • 97. bad-delimiter - 0 ( 0.00%) bad-json - 23398576 (47.30%) jp-error - 6575956 (13.29%) no-errors - 337233 ( 0.68%) Does it work?
  • 98. Summary • python-afl intelligently figures out meaningful input data • Treat multi-param input as pseudo file format • Possible to use property-based testing approach
  • 99. Property based testing * Overview * State-based testing Fuzz testing * Overview * Multi-argument fuzzing Agenda
  • 100. Thanks! • American Fuzzy Lop - http://lcamtuf.coredump.cx/afl/ • Hypothesis - http://hypothesis.works/ • JMESPath - http://jmespath.org/ • Chalice - http://github.com/aws/chalice • Next Level Testing (PyCon 2017) - https://www.youtube.com/watch?v=jmsk1QZQEvQ • @jsaryer