Fuzzing
Fuzzing is a technique for testing software that involves providing invalid, unexpected, or random data as inputs to a computer program.
Introduction
The Wake testing framework provides a FuzzTest
class that can be used to write fuzz tests.
A FuzzTest
can be run using the run
method with two required arguments:
class CounterTest ( FuzzTest ):
...
CounterTest () . run ( sequences_count = 10 , flows_count = 100 )
The first argument specifies the number of test sequences to be executed.
A sequence is an independent test case - all connected chains are reset after each sequence.
Each sequence consists of a given number of flows. A flow is an atomic test step that is executed in a test sequence.
The FuzzTest
class provides two properties, sequence_num
and flow_num
, that can be used to obtain the current sequence and flow numbers, both starting from 0
.
Flows
A flow is a single test step that is executed in a test sequence. Flows are defined using the @flow
decorator:
@flow ( precondition = lambda self : self . count > 0 )
def flow_decrement ( self ) -> None :
self . counter . decrement ( from_ = random_account ())
self . count -= 1
Flow functions must be defined inside a test class that inherits from FuzzTest
.
The @flow
decorator accepts the following keyword arguments:
Argument
Description
weight
weight defining probability of the flow being executed in a test sequence; defaults to 100
max_times
maximum number of times the flow can be executed in a test sequence; defaults to None
precondition
function that accepts a single argument self
and returns a boolean value; the flow is executed only if the precondition is True
How flow weights work
If a flow has a weight of `100` and another flow has a weight of `50`, the first flow will be executed twice as often as the second flow.
```python
@flow(weight=100)
def flow_1(self) -> None:
...
@flow(weight=50)
def flow_2(self) -> None:
...
```
That means that the probability of `flow_1` being executed is `100 / (100 + 50) = 2/3` and the probability of `flow_2` being executed is `50 / (100 + 50) = 1/3`.
Invariants
An invariant is a test that is executed after each flow in a test sequence. Invariants are defined using the @invariant
decorator:
@invariant ( period = 10 )
def invariant_count ( self ) -> None :
assert self . counter . count () == self . count
An optional period
argument can be passed to the @invariant
decorator. If specified, the invariant is executed only after every period
flows.
Execution hooks
Execution hooks are functions that are executed during the FuzzTest
lifecycle. This is the list of all available execution hooks:
pre_sequence(self)
- executed before each test sequence,
pre_flow(self, flow: Callable)
- executed before each flow, accepts the flow function to be executed as an argument,
post_flow(self, flow: Callable)
- executed after each flow, accepts the flow function that was executed as an argument,
pre_invariants(self)
- executed before each set of invariants,
pre_invariant(self, invariant: Callable)
- executed before each invariant, accepts the invariant function to be executed as an argument,
post_invariant(self, invariant: Callable)
- executed after each invariant, accepts the invariant function that was executed as an argument,
post_invariants(self)
- executed after each set of invariants,
post_sequence(self)
- executed after each test sequence.
The whole FuzzTest
lifecycle is visualized in the following diagram:
eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d6XLiSpb+30/hqP7TXHUwMDFk06ZzXzp6Ylx1MDAwMm9cdTAwMThcdTAwMWIvXHUwMDE4g5fpXHUwMDBlXHUwMDA3i1xmMoswizF03Fx1MDAxN5l/82rzJJOJy6AlJVx1MDAwNFx1MDAxNpi6XHJxo25cdTAwMTUgIaXOd75zTp7lX3/Y2/sxXHUwMDE4d61cdTAwMWZ/2/thvVfLLbvWK49+/EW//2b1+rbTUVx1MDAxZqHpv/vOsFedfrMxXHUwMDE4dPt/++tf50ekqk774yirZbWtzqCvvvff6t97e/+a/qk+sWv62MvLaveB9uFzpV2lrMJcdTAwMDanJYynh06/9HkxPas6KHfqLWv+0bt6n0My+/dYX1x1MDAxOcWzf4/s2qCh3sOQp4DrXHUwMDA1Z99oWHa9MVBfXHUwMDExPCU9r9lXPn7zb3tg9k5/0HOa1qHTcnr6wv74cdL5ZVXK1Wa951xmO7XZd1x1MDAwNr1yp98t99Q6zL/3bLdahcG49bGC5Wpj2HPd3Mev3P28XHUwMDA16Ht/dlxc31HrPT9K/Wy90bH6fc8xTrdcXLVcdTAwMDd6eSCY34e+xm62Nn0w/3SfoVP7eYbOsNWa/7Bl6edFXHUwMDEwI0RcdTAwMDA0X+a5WFDJ/e9eOp2piECBuEBIgPnK2v0jJVx1MDAxYoPpWZ/Lrb41X0F9XHLHfrlxy45HNFx1MDAwNtb7fFldkiXL+NGGb0O70HjIVyqTo/t+sfJj9r3f/mI+7cfBuZtcdTAwMDJcdTAwMTldg5dreHfb4ZmsPG1cdTAwMDPh/ZXP3y/3es4o7nmf72tH11x1MDAwNbvJ6pXhWbPfusR5dlx1MDAxOO+8P/82f1bDbq38sX6QcUolXHUwMDEzUkgwfzItu9P0P8aWU23Ol/xcdTAwMGauXHUwMDBi9uHSvHpcdTAwMDFcXHpcdTAwMTb/XHUwMDAzkkSmqFx1MDAxN5SCXHUwMDA0QIlcYlx1MDAwYsJcdTAwMTCxXHUwMDFk7kJwXHUwMDA3IVx1MDAwM1hcdTAwMTJKoVx1MDAwMXiEg1DgXHUwMDExJiGmkK5cdTAwMDI8z4V8XdrmwqOFRq3iYaNsd/b6nXK333BcdTAwMDb9vWrPmv7E/Fk5nUHBnuhcdTAwMWJBwPPuSbltt8ae5dZnTbfsul6RXHUwMDFmVXVcdTAwMGJW74d7WVx1MDAwNrZip9lcdTAwMTfadq3mZpNKuW+pW/hQVrN3q+qn1DVavWxcdTAwMWOycnp23e6UW7dcdTAwMGLuL1x1MDAwMnaQONIhXHUwMDA3N5aTh+lcdTAwMWW0zp7az+fx6ZBcdFx1MDAwZvKIXHUwMDBiUTPkuShzhjwsdshcdTAwMGJBXHUwMDFlRYJTwTk1XHUwMDAwXHUwMDBmQlx1MDAxYYo8XHUwMDA0XGLgiFx1MDAwMEpXQN6KlFx1MDAwNzPv9cPSPYf547vKmL3dIWf/NC41ndhcdTAwMTCSXHUwMDE3u5Z9auRyo0z/sX6brWwjNZnvMlx1MDAwZTVx5oVcdTAwMDeGLqr6XHUwMDA0XGKUXCJcYlx1MDAxMMR3XHUwMDAwXHRcdTAwMDGIkFxuXHUwMDFlQLGTXHUwMDExIC5t41x1MDAwM1xiZYJcdTAwMTBIXFxG4/YwU7dnPfWt16GlLvVPf977v//536VIXHR7TphcYimh+cPzkdJcdTAwMDLK8JOS4dZcIrBWejwpZ0a1TqVcdTAwMGZaZNC1XHUwMDBm6UDex+cjl9mh12WfyFx1MDAwMNxcdTAwMTA2WIJ454GFwVxyXHUwMDExRFx0Q8zER5ShMLQxKlx1MDAxMcNcZm/QXHUwMDAx65Toa+dcdTAwMTVnXHUwMDE1fTaUXFw/1nPpy5u4bNRpjlx1MDAxZXOjSefoQjxcdTAwMWSdXVR6XHUwMDE1dNDdRjYy32VcdTAwMWM2XHUwMDEyyOco7ZNg9Fx1MDAwMlxuXHUwMDE5hMeOjcJcdTAwMWQlJFx1MDAxMJCAcVx1MDAxM1x1MDAxZFx1MDAxMVx1MDAxZYpcdTAwMGYogUJcdTAwMTaCYFx1MDAxNXNt3XT0qa+fOsP23n/uge0lo1x1MDAwNXzhJ6PAjUVcdTAwMDDtXHUwMDA0ZPvdYqNSn9S6PVxmnMrlU6VcdTAwMTBcdTAwMDRazS63nU7NhzWEPEjDhjAhMPhFUOxcdTAwMWMj/fZcXEF/rjP6+c5vXHUwMDAxXHUwMDAwMmX0MU6IXHSAXHUwMDE4hUcqINKHuYG7doaqXFy22IW4vTg+uH22XHUwMDBisFx1MDAwMnm2W4jLUNix7t9cdTAwMWOlbtLX9/x5NFx1MDAxONSfLmvxXHUwMDE4KvK8Nny9rlb5ef6QPZT3R0dYXGZcdTAwMGZy28h85tWLxXxe3nPL4oz3uMFcdTAwMGKjZIfGMC+MciRcdTAwMDWURi8sKkpcdTAwMDFcdTAwMDCTjLlcdTAwMTTiltLe3/f+0fl8o/9UVZcy+G5cdTAwMWEkoYHCXHUwMDA1XFxcdTAwMTVJg3/f899mlPVcdTAwMTlpJvtcdTAwMTSFXHUwMDA3hILzXHUwMDE0Rur5Q0gpZj6C3Fx1MDAxNykuIFwis1x1MDAxZLFgnFx1MDAwNKWQVKRJXHUwMDE551i58Vxiclx1MDAwM2BBSlx1MDAxObCQK7tKMMHQXHUwMDBlv8uyKaKAc86MXHUwMDFiboRcdTAwMDScwFx1MDAxOa5cdTAwMDVcdTAwMTecXHUwMDAxsU3Bla5j+9l6/re9uSRM/zH7+z//Yvz2frj46VdA8ObnXHUwMDBikGSr3Fx1MDAxZlx1MDAxYzrttj1QN3qtLzKgY1x1MDAwN+Xe4MDu1OxO3fv8fm5vxzF+p1xuqTrUXHUwMDBisFx1MDAwZlJcdTAwMDAqXHUwMDFmXHUwMDA1ICHVRVLE5namlqByV693XG5Bwels35pcdTAwMDdkxOrU5lx1MDAxN1x1MDAxNbzgtFx1MDAwNn3DKtf8n6rj3J+5zVxiv5aJXGLwuKN8XHUwMDExZp0gXHUwMDFjq1x1MDAwN1x1MDAwNeNI4odOezmtwJKddd6sPuc1ckDObGRvXHUwMDBmvoOa6yO27FxydWGevGabR6hccjlcZni+wJ+RXCKMiVRyM//ykqZFQla2KI/eXHUwMDE47FxcWHftyuuTLN/fXV7fbmDDPPK8v8Buh9mAdsHOXHUwMDEwc5/BTlG3IHpcdTAwMGIqNuzMj2nLYSdcXMv8XHUwMDAxO7o+2LnCc6ZQ3KclXHUwMDBlKMCIuvD4Rditi4+XNpqneGw5o2BcdTAwMTjKXHUwMDFjXHUwMDFi+pqh7TOpXHUwMDE3sILfpPZcXGckvFx1MDAwMlx1MDAwMaM5wphcZt29Uv6pgFxmg1iO01x1MDAwN8D20Vx1MDAwNFxcOlmULuJcdTAwMDfLseVJpZhcdTAwMTPbXHUwMDBlMO6S4+meqcsvT1x1MDAxYWCIXHUwMDE5XHUwMDAwXHUwMDA2XHTwI0zxn9DpZC6nJFx1MDAxOaM7IcLrXHUwMDE3TzO4cnRA73L7XHUwMDA1eHz6yo57OC4xta9cboPJ1Vx1MDAxOFx1MDAxZVx1MDAwZrPpt1eUzedeXHUwMDBipVx1MDAwNFxir1TvdigvTci4p1x1MDAxODVdXHUwMDFmXHUwMDBlO7m3X4vwqCtNxVx1MDAwN0fClCMrXHUwMDEwhrHRaH5IW45GP91R5P/tXHUwMDA06Vx1MDAwZVx1MDAxYqxMV1x1MDAwNO4zP4ZcdTAwMDOClLW7XGZcdTAwMTZ/Qbr7+z86+u+ByJM5XHUwMDFjlCjzLeCNUObzXXIk5Fx1MDAxNvh34Vx0huqZKFx1MDAxN1x1MDAxYon4wDtcdTAwMThlby5AXHUwMDA3XHUwMDFkXHUwMDFlNsdcdTAwMDcvd+SWXGbVc99y4Kkl8O/WXHUwMDEy5M5cdTAwMWVKnFx1MDAwYpGBXHUwMDBigz6eMkNcYqaM4e928sBRsZi9XHUwMDEwvfdiuXHcfbt7IJfnL3G5qeyMnIPK8KrSS2fPT/jTY7mUSYLzhjYqturoofKUyVx1MDAxZHWqd48vtcfCr8V5XHUwMDEw4vCseiSEQJzjOHvWUY9p+8HnzykniK1cdTAwMTN8IDrp4lx1MDAxM3zKweZcdTAwMDK6Y1u/J+7rOv1BaFx1MDAxNtxcdTAwMDY8vlx1MDAwNURcdTAwMTFIbDNcXO7qnMdIIOI+szWBsjMxQ/FRd1C5yljOsXPjXFxcdTAwMWZcdTAwMGWOXHUwMDFk++34NPu8/ajzpMd+lFfJtWHuS2VaP1x1MDAwMcm5clx1MDAxMVx1MDAwMEpcdTAwMGWPK5Jhb3KLx87jSVx1MDAxM1x1MDAxNE9bTDYrp/n05LvJcJTPl09cdTAwMWFHz7dcdTAwMDfZq/akWs0/pItXv1x1MDAxNlx1MDAxOVJcdTAwMTjKhVjo7EZcbuPboeaHtPWgZNhcdTAwMGZKub6dXHUwMDA2dz2lqUzrc6dcdTAwMDFgTDAnrvjNr0uEXjbTS+Sv4+lZ/YHTc1x1MDAxNyqZq4eSZcNoXHUwMDBlWVR7NLtmI/DiaJ6oZFx1MDAwMm2JplxikoJBqlS3O276Ya1cdTAwMDFDoVx1MDAwNUpBXHUwMDAyqaRcdTAwMTIpXHUwMDFilktkyL/zOF27lFx1MDAwMfdjXGJNXHUwMDE5UFx1MDAxZVx1MDAwMVZLaqxXXHUwMDAyLlvNp0BcdTAwMDWT6iFisk2JQElnXGaEyNv0Uy1q81NcdTAwMDRcdTAwMTgvsSSBaKt2b7b773rnM21cdTAwMDCkXHUwMDAwIeoxcaVqsUJcdTAwMTlnXHUwMDAxXHUwMDE58KRcdTAwMDSEXkKkKll0XHRcdTAwMThSQjmBXHUwMDE46axcdTAwMGKIg3KYVPJBXHUwMDFjJz5KK0lcdFNK5JFUeCBcZlx1MDAwMa9SokSkMFx1MDAxMFx1MDAxNFCqXFw44jI4Z1xuStKUpGqxifpcdTAwMDMwyFxyXHUwMDE5Tkrv6cgzI0JQqEszdupqOXVcdTAwMDW5kEqWkDFhXHUwMDFmYlx1MDAxMa6wlC2kN+DiWHy/pMJCXGaldO5cdTAwMTeBglxi9X/uVVhcdTAwMTCmiMRYZ+9hyVxi4mLhXHRD5Xl6woAoJ6lcdTAwMGX9OiBcdTAwMDFF6c6mklxu4Vx1MDAxMFx1MDAwM6pcXFx1MDAwMITw3Fx1MDAwZZ0rs5RcdTAwMDCMXHUwMDExwHROo3rKNJhOteGkKXVcdGGijSBmglx1MDAwM8HjezNnhFx1MDAxNq5cdTAwMGZcdTAwMGXIw/CAZo8qz9dHR6WH7VFcdTAwMTkhm8tU+JxcdTAwMTnlxK0zrlx1MDAwN1xycT1DUJ1BwqaIS8qdWTGOUCy2XHUwMDA2z1x1MDAwZs2nyUtZytd8/6RcXGqzLfTLlSyHJkpoe1x1MDAwYmNcdTAwMWOrrirqrrddlJk3/09p7TVcbvKiXHUwMDFh9U9BXHUwMDE2XGIyXHUwMDAxMPl9bs7qUma9w/kn/cfm49NcdTAwMGJUrqnw2ne1q7OHIKGpXHUwMDEwXHUwMDEwqUeBIUTxU/86qDh65s3O+dXoiVxm0eTo4u02s/WIXHUwMDBi7MgyideIOFx1MDAxYURcdTAwMWNcdGRDMFxilXVcdTAwMDFlYoGwXHUwMDE1maNmtUfg7KpWLmdunEPRXHUwMDE4XHUwMDE1x7nD2JVtt71b+5lcdTAwMWVcdTAwMTZL1mk+c1RqZ67LjV8rUszDU4UgXHUwMDE1XHUwMDAyXHUwMDEwgXl8fJhXc8vxIbDX3+VgnYzkqqyOICSkY4PKadtooJgsIWxfI6Spev/zRklogeo2JVx1MDAwN6krXFydePSedyiygFx1MDAxMFA5LTK+21x1MDAwMlx1MDAwN8W7dPMofdWynlx1MDAwZY/tYXnYLF1vO7I48bstXkcmcbfF2FwiJZh4Llx1MDAxOeNcdTAwMWPyxIy9XHUwMDE1ued8fEybtqhcdTAwMWOc0MbonmdPyPtV7FQgm1x1MDAxY9yfOaVcdTAwMTdr/FguvGbKnbP93kGi3LOMOliJe5TzXHUwMDFlmi5HMFx1MDAxNoyxJdLlzKu57VxiYd5yUu7ZtfymdFx1MDAxZMmBXHUwMDE0Slx1MDAwNDbpXGZtjnum6S/f6FxyLdDkxmydOO7Qz6z4wTMqXj1W+42y3bhcdTAwMDBcdTAwMGbnXbpfXHUwMDBmblx1MDAwMlx1MDAwNHtcckBcdTAwMDCBR1x1MDAxNKlrg/ZTgKjJeNk1I1xy6zXAmZSEXHUwMDAyXHUwMDEyiF/qs6PQmjRcdHXjYLRaQ8QktXrQbtPv/vAgbbW2oy3reVx1MDAxMFx1MDAwMaGB0zViMrTjqOcmXHUwMDAyttz0qiMpKqK2XHQqay3sSVFOXHSjy2S4vT10jnGLZ61BXHUwMDA1XHUwMDE1rs6uT+rD4uX2yHlY+NmfViog8v16kixlSHBwRVx1MDAxNWb93VxilcSd3LZVtU1cdTAwMTe5l6t692pQyp3l705Kk0unwbfRXGKTMtRcdTAwMDbTpUKSXHUwMDEzXHUwMDE5X7rNN73t0i283U5cdTAwMDVZ59ZcbnR1po7KXHUwMDE0g0j5Spjx30NcdTAwMDAgmCl2M+zs2Z23cs8uq6v7r40miC1QwX728F9qJMqiQ1x1MDAwMlxcXHUwMDA2ko3mwTb1KUax+v7/TMtcdTAwMTSn/fODQvXuXHUwMDFjv6fzhdb47OXM2nqsebClj5FkrVx1MDAxZVx1MDAwZo5VXHUwMDFkpLxNXGKQK7Ple1x1MDAwMlx1MDAwMpX8ZY+lr4qw0Vx1MDAxZmT549NcdTAwMTBcdTAwMTROYrdZs586k07m4jl3VK6g4aibr7dO00lcdTAwMDVcdTAwMDQkpFx1MDAwMK5/e5Tz0GJcdTAwMDJcYjGCXHUwMDAyi1htkKOWc9tcdTAwMTFC/bs1kqw1ZoZMfFx1MDAxNNwhhYhChGRyW/3RfCQhYUgsIXDL8JF5h3Su5L+hhmeBNjftkVx1MDAwNq73XHUwMDBizFx1MDAwNHFE57tppVxigPHNwMbTXHUwMDA1LZ9cdTAwMWa1XHUwMDA3zczr3fPTy9FRw9p6J1x1MDAwN0JBfchT+mituVx08bpcdTAwMTOp9Vx1MDAxN4C7XHUwMDFiaHxcdTAwMGY7XZXHMH/brTRQ57B8J+xCxz5cdTAwMWbEZaf+6T56z4yd8lx1MDAwM1x1MDAxZj3XOs7oqDNJtlnn+sPVIJh6OU9EU1x1MDAwNty01UlskJiXc+tBXCK9k1Ogpzl34lx1MDAxMGGm6UdcdTAwMDGIUFxmIaBMbLK8dHNcdTAwMDFrd9PL//iPjbLSXHUwMDAyRVx1MDAxZdWeU13pXHUwMDE3+FxiR3hKgCguYlwiVsvGn5nxgMrz++zxSZpcZp6Kt2+vxfb9eNuhRoG/gI3BtWItXHUwMDFlXHUwMDFkXHUwMDExqbOHXHUwMDEz3Fx1MDAxY1qRjVx1MDAwZeDLVemyUeO3XHUwMDE3XHUwMDE3h/vNm0zOOnbistGTzDt1WL3JXFxfYyuTO76/Q01cdTAwMWWPjVwiz3v0clu7ntxcZlx1MDAxZSRcdTAwMTnlSLr6UD1cdTAwMWW3fzGWwyS0klx1MDAwMFwiQokyXHUwMDA2cXzomVx1MDAxZtO2Q1x1MDAwZvvj3VxmrtVcdTAwMTI0pVtcdTAwMWJ8MImlxJQvXHUwMDE18v51eO6zXHUwMDFmz4Y5blx1MDAwMTmEdVxyXG7jt1x1MDAwZrFcdTAwMWZcdTAwMGbO2OT5XHUwMDFk3Z/mj5xitS+v2dt7cFx1MDAxYjZkXHUwMDFjXHUwMDEwxd6tWFxigoMhITJkNrt2XHUwMDFh/533YoPbPIbiu8/WJFx1MDAxOEHdmstYVVx1MDAxNdHWUPGgcppjOcVcdDHe2fhxcGbfj8ZcdTAwMDWb4PPTXHUwMDFiXFzsX/TiMlx1MDAxMypksq/3LTv34lx1MDAxNJ6bx1Y236BbxkxRd1x1MDAxOUBOMIFBXHUwMDE5JX73XHUwMDFkXHUwMDA2YUN2U4KWyWBghHLd38Q8LSHUUKBcdTAwMThcdTAwMTNE4mVcdTAwMGVvipM+aebI6SyXwrDRuUBcdTAwMGKIw89G05uJQFN02GVBp1x1MDAwMpyCXFxgXGawrp6U3k1Sd/bkvC0pXHUwMDBiOcI152BcdTAwMDc1I0GFl/1CXCKIQOaBJViGpqYgoFveUY7iRKQ2hcFk+1x1MDAxNGDiXHUwMDExNyq8db+ELTxDqMROP6WuS1xikGBifVx1MDAwZaJjLHvullx1MDAwMpAhzpiyV6VywSGBwerdeW+GjU4/iNM4aVFbXHUwMDE0j3pBNJh8OFx1MDAwN5iprminTtyLXHUwMDFjrk4ox1xcMFx1MDAwMMw5iVH2LmerzWj+JZSJ59v7ZCNjUFx1MDAxNrZcdTAwMTfxjEFBXGZcbiRcdTAwMTlFUi3avFB1XHUwMDA2ffh9wH/v9tN2v3rzbj/cVFx1MDAwZlx0bt2Nz0lcdTAwMTD45omD1GdYXHUwMDAwYJrFbigqhC5q26HfvdLh6MdcdTAwMDBxiEUguD790fBqXGaIKVGuXHUwMDAwXFzJoF91Km73spWvXHUwMDE07+BIqpXs9Vx1MDAxYaObXHUwMDA2iOvuXHUwMDE27LOW7D7et8jpoNe5zz11u93XXHUwMDA0XHUwMDAyvM2n91x1MDAxZWneT5x7Wm6mh6XC/XPmJt55XHUwMDAzemStbrR59Vx1MDAwMpA0zFx1MDAxY5T+bVx1MDAwN73hYlx1MDAxYf9OXGaE7CpcdTAwMTD4d4akceAuhpxcdTAwMTBMYSDF6Yfujlx1MDAxNF6Gi1x051x1MDAwNK5cdTAwMTZqWrcznW61XFzJkXv/6FjvVnWofsSV0rl6lcCXZlx1MDAwZc7vy+diL2Arv4vtu8X5XHJGoC96NGn0tEFf00pcdTAwMThkQ9M+3W787rJUXGI4Vlxc6N719PT/XHUwMDBiN4SF+o8yuk2xrWT9auj+OnZ1y/q6Kbx8d6voXHShe273XHUwMDE4IChcdTAwMTV+MGGIXHUwMDAxLJRfXHUwMDFmMJKRSFx1MDAwMcZcdTAwMTjkelx1MDAxMocyaYLSkaxlfMjesm/17lx1MDAxZHwjtXb2nL+e54/u4tCwcMnlh1x1MDAxZZjHf2axbEOdzK5cdTAwMWEvnIApQpyo/0zWb3D/x91cdTAwMDaDccDINlx1MDAwMf6Tf1x1MDAwN73hr1ePN73oXGLQRI8yjGZPZalcdTAwMDJCXGJVZlx1MDAxNkHCN8yeoFx1MDAwMIZAXG5JgFx1MDAwMdA9Xbn+z1R8ttsgWpZdXHUwMDE5ZUBcdTAwMTJ3Xz53hknovlx1MDAxMZdcdTAwMWND9Dum1lxicdMvxOdn+DrZhlHq5WW1+0D78LnSrlJWYYPTXHUwMDEyxiGUXG4kRUJcdTAwMTEqhpRL9afrcuPFncIuXCJ6TOGeN/hFmOSUYUZcdTAwMTBcdTAwMTZYsDjBr4SJ/OFcdTAwMDXuv1x1MDAxY9D+2aSHj1x1MDAwZl+bk+Px+34snURdXHUwMDE1K1x1MDAxZqlEc1x1MDAwN3DuS4uUwJzovuCAXHUwMDE4m8kzlJJKpymAUFx0XHUwMDE5dIVwdirJ/VxiQlVcdTAwMTLlut9cYsUm9jd0XHUwMDAxnzVcItGNXHUwMDAxJVlX99zv10nIXHUwMDFi+mZcdTAwMGJcdTAwMGZcYlx1MDAxNdaP4/1ymqRGXHUwMDBid1x1MDAxZtZcdTAwMTnejs5cdTAwMTaNwj5nPMVcdTAwMDHkUFx1MDAwYlx1MDAxZnXHXaeFxlxmpjBnTJmlXHUwMDE0K1xyXHUwMDE33PKCXHUwMDE0q+PVsYTq3D9cbkz9NyBcYjnLTjG4n09EJFx1MDAwMGFMaXCO0Vx1MDAxNP2hMXGsIEBcdTAwMTWIf696YVx1MDAxZmKeglxiQUpcdTAwMDBkSN2rr6228IpcdTAwMWRAi89cdTAwMTgqztOPXHUwMDAzkrxcdTAwMTnVXHUwMDExZqFEJ4Z6LVx1MDAxNEUtXGZcdTAwMTP13KighMO5XzS3UFxmO/ObbaSNIFx1MDAwZd3exUAwyfhcdTAwMTKtUFx1MDAwZuHFpFxizmnrXHUwMDFldvJcdTAwMTdPt3LYvC5tj4ZcYilfXHUwMDAyXHUwMDAyXHUwMDA0klx1MDAwNF1cdTAwMTHWxGd1YUOSrqGmgkGGXHUwMDEx+/Zcbr+zSa6Mc1xi2dBpPtw99Wuv1zD2POZ1bbmNLl56g0y60Dy06oUqPXWuqlx1MDAxN0nNY1ZIRcxcdTAwMTUlWFNNXHUwMDA1cimEQHhJUFx1MDAwMZWpJOPMk4l6TFtcdTAwMGY9iXzAW+NMZmxKXHUwMDE4MFW1c0EkXHUwMDE1S1xySF+9omJ5cZs/1C9Xtf9p9rfNV7cvIIvI6nb/da9OgFx1MDAxMIf3XFyDQqqHIyiIXHUwMDBmw/rgoVsrlZ/T7JE2L/MjIN9791tcdTAwMGZDXHUwMDE46Mlcblx1MDAwMV1cdTAwMWZcdTAwMTCN7cBccpMkdJ9pwcl3M+B7efJO+bh1e3V8bOVygkv4crNcdTAwMDGmijyvcz5cdTAwMTiObp/eXHUwMDBmT1x1MDAwZi6sa6vb4fmnSlJcZqiMXFzoSsFaV1UhQuFcZkiQ1FeB4vc+Mj+mrYde0PhcdTAwMDR0fcanO85cdTAwMWVZPK/cX2X5b6bR2PLy9jVcbpzTyGbbjS8gXHUwMDA3P+O5L3N1gkOQhVx1MDAxM5yeaSiwXHUwMDAwcVx1MDAwMpk/x1xc9qtcdTAwMTl1w6dAlN9cdTAwMDaFs8pr53Aotlx1MDAxZWWAXHUwMDA1XHUwMDFhKHlwl7ipaSopMzBcdTAwMWNSIINJmporUtxonKPFKmik5V3n1m6fTkpcdTAwMWQr9sSLL+Q/fiPFyU00L1PgXHUwMDBib17GKdGD0GF8ijM/pq1cdTAwMDdcdTAwMWb3Z49KT1elxMHn4rMoP1x1MDAwZulcdTAwMDFPjG3IzVta4L7o5ulcdTAwMTbhW+HnLWBcZmNv83U4eu6iXHUwMDAyf6STM1x1MDAwMsQy3WPeXvO0na4jkLux+EvhXHUwMDFjw9uLxrYjkVx1MDAxMz9cdTAwMGJCSOXacIhILDdcdTAwMGbpUmKM3OVcdTAwMWPf5Oe10vnbYmHYKYLjk0n/xXm/7Vc20OUl8rydtJOv347gdVxy3o1cdTAwMGVykk9qXHUwMDEzKyFcdTAwMTJkknCB1u/ncVx1MDAxY5FIh1x1MDAxOeBgme4x5se09dBcdTAwMGJEWOB872wzLW5cckM9dPJcdTAwMDMhdFP9O4nSwXRcdHFLklx1MDAwMr+jgedcdTAwMDKWiKa+yFx1MDAwZZ5xlE5kXHUwMDEyXHUwMDAy0XN/XHUwMDExXHUwMDA0XHUwMDE0sWlcdTAwMWGXTzY5S+mESS5cdTAwMTmSXHUwMDFjXHUwMDE56vqVYZtChEjEiJ5Jplx1MDAwZVxiXG5cdTAwMWNFyHOWXVx1MDAxYfKyWVxiatH0XHUwMDA0dlNdLqWh5VxiXHUwMDE4ScjoOoqDJIXEVVx1MDAxMfh9aVxihKSQXHUwMDEyP4CQ4FxcQOhNXHUwMDFh0N6+svcoXHUwMDEyXHUwMDAwSkAxX3zCUHGeflx1MDAxY5Dk+Vx0XHUwMDAzLJtYSma09tjz5Fx1MDAxYXBcdTAwMDJcdMdYj+hcdTAwMTaQQVe1xqxcdFx1MDAwMFx1MDAwMVxcKFxyjJkgXGJcYlx1MDAxOOx+lGxcdTAwMDJcdTAwMDRQ66lcdTAwMTCMpX5cYlx1MDAwNGDAg1x1MDAxOZokpWd1fL6EYIFrSjhrK3o/OkphSkZSQImS0Fx1MDAxNUJCYN++JWAyxbm+XVwiuJJcdTAwMTZDby5I1d1cdTAwMTJcdTAwMDZcdTAwMTHjSJE/NzipTH1FibaQTJlFhLKdwlxcNsVcXJdyYFx1MDAxMuzJqz9cdTAwMGLPdFF8p+RPoWKVxK2I6Up6b9ddWvJd+pKilDSKnn4xpUmlVIYgRIQrR3hhh1x1MDAxNMpcIs5G+bKnXHUwMDBiXHUwMDA3xvTq/JDYhOaFzVx1MDAxYpS+T9euXHUwMDFlryuZcd2B3XZ3YtS8MEWwpFx1MDAxY1x1MDAwYqo8WCVApjR0XHUwMDE0slwiS2re6P3y+c+ZXHSCXG6gXHUwMDA3W+tcdTAwMTXkXHUwMDAwkbVcdTAwMTe5RVx1MDAwN4Uj+75cdTAwMDDmLXNTa6d0qy7iXHUwMDEzXHUwMDEy68KroG5cdTAwMDVUXHUwMDExvpRcXIGOqpsztUGkQklcdTAwMTnEUiiTSomv3OnWZY1RpVxcicBcdTAwMDSakuVZeJeYqckkXHRMvIDnXHUwMDBiIdVkrVHiyXbX+lQhnSk5Jlx1MDAxMOqU1sUn8KpQ7DlcdTAwMDNZeHi4+E9P51x1MDAxN/xNaNDooO+eK1x1MDAwMdZoOjKsXHUwMDBijFx1MDAwNOaQSUJWNFWja/r3fNVEYmq96V1vZVx0MJNcdTAwMDFccrl6JFxuXHUwMDAxSn1CpfPXrUGjM0eiO2dhnuKY64GRyldcdTAwMDH+9vzQ1D8gRTlSmldcdTAwMDFcdTAwMTkqo1x1MDAxY1x1MDAwMlOewK6MYOnOWnp7l6JgXHUwMDAwVCvGYFx1MDAwN835pGOl2mjybfqmvVxyxPc78OHSpl/7KNEs/5Vcciq3flBOnWD6XHUwMDBm7dwxXHUwMDFj1Fx1MDAwZiuWPEbnhHiUXCJQdqYuViPahGOu8E7ENSSsk6K35Fx1MDAxN+gkmdL1v1x1MDAxODBlLit73+dAk2AvXHUwMDAzXHUwMDFkc+FUfZ1TobwzXCJNc9vcaVx1MDAxYzut5H5cZuFaSSojQVBmXG4rSteP+r1kKZT2UFxuLU467JJqXHRw92jjb7PkwiVu+rFcdTAwMTa2TWim2Epharlo45phouxcdTAwMTemXHUwMDFlT1ArXHUwMDA05SCWZlpowrkrwrlUvjthQGfsKuuJXHUwMDA3LmLtqim6mXnkXlxi8u1+oKB5xFxmXHUwMDFl5a5t8ZJ6XHUwMDA3M6yseWR0IEW42lx1MDAxMXxabc2S386YVlVugdphXCLk+19XMcuXOEZ3XHUwMDFi9ylcdTAwMWaGhfLuXHUwMDA1R5josu+g8jFcZllIXHUwMDE29ye2cpFf7Fr2qZHLjTL9x/ptNp5JXCKYXHUwMDE398TV/zairVx1MDAxYdx5QUt3XHUwMDE4VVx1MDAxZbXyRYXJ4CDhUXmOXGKF2jP/3Vx1MDAwMt/j9sCNeD2xe6xMW6xgXGYkpkhcdTAwMGaiXHUwMDE2XHUwMDA2dK/o9EDiSIdcdTAwMWPcWE5cdTAwMWWme9A6e2o/n4d7XoBcdTAwMTGkbDLlXHUwMDBmXHUwMDBiXHUwMDFhx/FKWMO0r1xug8nVXHUwMDE4XHUwMDFlXHUwMDBms+m3V5TN515cdTAwMGKlmFx1MDAxYcbb5oWwYJtcdTAwMTdDw1S0a5i6bFx1MDAxZlx1MDAxN0xcdTAwMDTCWJjaNVx1MDAwNJs5zlwi05hcdTAwMDNcdTAwMGVo8v7MtuhcdTAwMTfPt5GrXHUwMDE3w/rUyz6agEsni9JF/GA5tjypXHUwMDE0c2Feg1wibsBcdTAwMDQhOs6tXGaJ+a7tXHUwMDFj2zglXHTlXHUwMDAwKq+MMO3pRCucdXZ8KdW7XHUwMDFkyktcdTAwMTMy7tWOrtP14bCTi1x1MDAxN4/1q1x1MDAwMWaYQWRcbrfuXHUwMDFjjKW7tkCqi1x1MDAwNqnJw+Ch+VJCXHUwMDEwKlx1MDAxOMBratvy/XrAY2agb/YvYmtcYk3+WOexybmmXGJqXGKSwlQo/U/Vo8eIg7U7XHUwMDFj+LZ3az/Tw2LJOs1njkrtzHW5sZpcdTAwMWXgwd6TOz2QRKCBYIKQeZ5cdFx1MDAwYjVcdTAwMDcgwVx1MDAxNENPR66dXHUwMDFlWJ9cdTAwMWXooOLomTc751ejJzJEk6OLt9tMmFx1MDAxZVx1MDAwMIIhpVx1MDAwMSSjVLuFsVpOJoz7l8b4snl1U3hrVcDg8OHw3MrCVlx1MDAxMPfBts1Meid3iqBcdTAwMTNATT3cdlx1MDAxOSqhXZuxXHUwMDA0kiBpzItcdTAwMGW8Od+/kHpiXHUwMDExpdvE87NBt/pd13P5Nbo2f1xcdVx1MDAwNGhscnB/5pRerPFjufCaKXfO9ntcdTAwMDcxydJbL8OxoVxiIcVcdTAwMDXgXHUwMDA0IMFcdTAwMTjDXFxQQ5RcdTAwMWXB3Vx1MDAwNuHydjRhSFx1MDAxMlx1MDAxMJz6rY+KXHUwMDE4S6KVNIVryPXaXHUwMDE2XHUwMDA23Vx1MDAwZlx1MDAxNzn9QjDZXHLC5VlcdTAwMTVcdTAwMGWKd+nmUfqqZT1cdTAwMWRcdTAwMWXbw/KwWbpcdTAwMGUmXqFcdTAwMTTwvtyE6tldhOqBQqLLLDBHWKyxYeGHxiBt+8Qqllxu+2J0VrtcdTAwMWY3m+yuNohHs96UXHUwMDAygYJcdTAwMTlcdTAwMDU7nl2KZ1x1MDAxMeBKXHUwMDBiSGNYzT3s1a9cdTAwMDZcdTAwMTCTXHUwMDE4M7iuMaM7ot1cdTAwMGLCZtItXGZB7cWy7kD9+qxylW/dyphESzy44dLQi9xcdTAwMDCb3ZjNJVlVQC5cdTAwMDTA5p7C4Vx1MDAxNdJcYlx1MDAxMIrWNmVzXHUwMDFiKNXDoIlO2fye3uCWrFrj0sX9oSztn1WuXyb3znO82lx1MDAwN+XlerAogWE6SVx1MDAxMIu7kbdLY1Fwhlx1MDAxMFx0OovTRVx1MDAwZsVcIlx1MDAwNEjq4uht8iDXt2OU7MTb78Gi/dSZdDJcdTAwMTfPuaNyXHUwMDA1XHJH3Xy9dZpeXHKLYi6hUZu3u7lAK6SjMoApNHmbXHUwMDEwh3ftwXquXHUwMDFixGsoc4dbUVjk3b7dyFx1MDAxOKCeOO2fXHUwMDFmXHUwMDE0qnfn+D2dL7TGZy9n7nYznqRP5fxiJpV/wCFcIq7chr0tXHUwMDE4P21dOPvNqzNayjSv65XO2GHDM8O8MMOQPe6tlpEgXGL73ZC9pdxIXHUwMDA2JNOzXHUwMDEwTPhcdTAwMGWGmGZzvyhUWiFeXHUwMDBm9E07kb/LXHUwMDExe527trBE44JWut2MLc6dw/bBWSzI+KdZ7SDzVchcYj1hhbhcdTAwMTcy3iBaQNTDkFKuKfKyUo/e3zdoXCL7s0Xbl9iDXHUwMDFhXGKRaVx1MDAxNKWyjiRV0lx1MDAwMIWySVx1MDAwMFx1MDAxM6bhIbtcco4lTU5cdTAwMDQ4kIBcdTAwMDBjolx1MDAxMFxmou5cdTAwMTNfXGYxXWeTPLqW7sS3XHUwMDFli3M/Qt70XHUwMDBif/PuxjItkVx1MDAwMFx1MDAxNEKZqFx1MDAxNEMoiNKZsSqjXHUwMDEytkJPSXZ8lreHpUH9/Vx1MDAwMlx1MDAxN9uVq2pzXHUwMDFji1JdzmZgMMqOU1fjVI1ehXpTzFx1MDAwN4VmXHJcdTAwMTBCgW58saYtzVx1MDAxZKP6MVx1MDAxM918J1x1MDAwMjOS+muKRZBQdzuAS2GG4mmrJ2nsX1x1MDAxMFx1MDAxZSdV3p6kXHUwMDEy4FV8t5DuvV9cdTAwMWUoXHUwMDE24Ks1XHUwMDAw8tffXFz8w89cdTAwMTX6Ue52XHUwMDBiXHUwMDAztT4zNv7xZlujg6C0//F5+tLHT1x1MDAwMa0l3ZraY7/94bf/XHUwMDA3y588PSJ9
Chain snapshots created pre_sequence() ⚡ sequence_num = 0 sequence_num < sequences_count flow_num = 0 flow_num < flows_count post_sequence() ⚡ Chain snapshots restored pre_flow(flow) ⚡ flow() post_flow(flow) ⚡ false Run invariants? pre_invariants() ⚡ sequence_num++ flow_num++ Done All invariants executed? true pre_invariant(invariant) ⚡ invariant() post_invariant(invariant) ⚡ post_invariants() ⚡ false false true true true false
Example
Putting all of the above together, here is an example of a FuzzTest
that tests the Counter
contract:
from wake.testing import *
from wake.testing.fuzzing import *
from pytypes.contracts.Counter import Counter
class CounterTest ( FuzzTest ):
counter : Counter
count : int
def pre_sequence ( self ) -> None :
self . counter = Counter . deploy ()
self . count = 0
@flow ()
def flow_increment ( self ) -> None :
self . counter . increment ()
self . count += 1
@flow ()
def flow_decrement ( self ) -> None :
with may_revert ( PanicCodeEnum . UNDERFLOW_OVERFLOW ) as e :
self . counter . decrement ()
if e . value is None :
self . count -= 1
else :
assert self . count == 0
@invariant ( period = 10 )
def invariant_count ( self ) -> None :
assert self . counter . count () == self . count
@chain . connect ()
def test_counter ():
CounterTest () . run ( sequences_count = 30 , flows_count = 100 )
The test performs 30 test sequences, each consisting of 100 flows. It tests with two flows of the same probability: flow_increment
and flow_decrement
.
The invariant invariant_count
is executed after every 10 flows.
Generating random data
There are two ways to generate random data in Wake fuzz tests.
Warning
Do not use the random
module from the Python standard library to generate random data in fuzz tests.
Instead, use random
defined in the wake.testing
module.
Flow arguments
Every flow function can accept additional arguments to the implicit self
. These arguments are generated based on the type hints:
@flow ()
def flow_set_count ( self , count : uint ) -> None :
self . counter . set_count ( count , from_ = self . counter . owner ())
self . count = count
Flow argument types can be any of the following:
integer types ranging from uint8
to uint256
and from int8
to int256
, including uint
and int
,
byte types ranging from bytes1
to bytes32
, including bytes
and bytearray
,
List
, including List1
to List32
helper annotations (e.g. List16[uint8]
),
bool
,
str
,
Address
, does never generate the zero address,
any Enum
, including enums generated in pytypes
,
any dataclass
, including dataclasses generated in pytypes
.
All flow arguments are generated non-biased, i.e. the probability of generating a value of a given type is the same for all values of that type.
For types that have length, the length is generated in the range 0 to 64.
For generating fine-tuned random data, it is recommended to use the random functions from the wake.testing.fuzzing
module.
Random functions
Additionally to the methods provided by the standard random module, Wake testing framework provides a set of random functions that can be used to generate random data.
Warning
Never import the standard random
module in Wake tests.
from wake.testing import *
import random # never do this
Wake already provides a custom isolated random
instance that can be imported from wake.testing
.
random_account()
random_account()
returns a random account from a given chain. It accepts the following keyword arguments:
Argument
Description
Default value
lower_bound
lower bound index of chain.accounts
to choose from
0
upper_bound
upper bound index of chain.accounts
to choose from
None
(i.e. len(chain.accounts)
)
predicate
predicate that the account must satisfy
None
(i.e. no predicate)
chain
chain to choose the account from
chain
random_address()
random_address()
returns a random address. It accepts the following keyword arguments:
Argument
Description
Default value
zero_address_prob
probability of generating the zero address
0
random_int(min, max)
random_int(min, max)
returns a random integer in the range min
to max
. It accepts the following keyword arguments:
Argument
Description
Default value
min_prob
probability of generating min
None
(i.e. 1 / (max - min + 1))
max_prob
probability of generating max
None
(i.e. 1 / (max - min + 1))
zero_prob
probability of generating 0
, if min
< 0
< max
None
(i.e. 1 / (max - min + 1))
edge_values_prob
value to use for min_prob
, max_prob
and zero_prob
if not set
None
random_bool()
random_bool()
returns a random boolean value. It accepts the following keyword arguments:
Argument
Description
Default value
true_prob
probability of generating True
0.5
random_string(min, max)
random_string(min, max)
returns a random string of length in the range min
to max
. It accepts the following keyword arguments:
Argument
Description
Default value
alphabet
alphabet to choose characters from
string.printable
predicate
predicate that the string must satisfy
None
(i.e. no predicate)
random_bytes(min, max)
random_bytes(min, max)
returns a random byte array of length in the range min
to max
. If max
is not specified, it generates exactly min
bytes.
It accepts the following keyword arguments:
Argument
Description
Default value
predicate
predicate that the byte array must satisfy
None
(i.e. no predicate)
Launching tests in parallel
Wake testing framework allows running the same test in parallel with different random seeds.
Multiprocess tests are launched by setting the -P
flag specifying the number of processes to be used:
wake test -P 3 tests/test_counter_fuzz.py
If a test process encounters an error, the user is prompted whether to debug the test or continue testing.
While debugging, other processes are still running in the background.
By default, nothing but status of each test is printed to the console. Using the --attach-first
flag, the output of the first process is printed to the console.
Standard output and standard error of all processes are redirected to the .wake/logs/testing
directory.
Reproducing a failed test
For every process, Wake generates a random seed that is used to initialize the random number generator.
The seed is printed to the console and can be used to reproduce the test failure:
wake test tests/test_counter_fuzz.py -P 5 -S 62061e838798ad0f
A random seed can be specified using the -S
flag. Multiple -S
flags are allowed.
Non-deterministic tests with set
Python built-in set
is an unordered container.
Given the unordered behavior, the following code will lead to different fuzz test results with the same random seed:
items = { 1 , 2 , 3 }
item = random . choice ( list ( items ))
It is highly recommended to use OrderedSet in fuzz tests instead of the built-in set
.