Fuzzing
Fuzzing is a technique for testing software that involves providing invalid, unexpected, or random data as inputs to a computer program.
Introduction
The Woke 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.
@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 woke.testing import *
from woke.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 ( Panic ( 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
@default_chain . connect ()
def test_counter ():
default_chain . default_tx_account = default_chain . accounts [ 0 ]
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 Woke fuzz tests.
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 woke.testing.fuzzing
module.
Random functions
Woke testing framework provides a set of random functions that can be used to generate random data.
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
default_chain
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)
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()
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)
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)
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
Woke testing framework allows running the same test in parallel with different random seeds.
Multiprocess tests are launched using the woke fuzz
command:
woke fuzz tests/test_counter_fuzz.py -n 5
Info
The command woke fuzz
does not utilize the pytest
framework to collect and execute tests.
As a consequence, the pytest
features like fixtures are not available. Test functions must start with the test
prefix.
Test classes are not supported.
If a test process encounters an error, the user is prompted whether to debug the test or continue fuzzing.
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 --passive
flag, the output of the first process is printed to the console.
Standard output and standard error of all processes are redirected to the .woke-logs/fuzz
directory.
Reproducing a failed test
For every process, Woke 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:
woke fuzz tests/test_counter_fuzz.py -n 5 -s 62061e838798ad0f
A random seed can be specified using the -s
flag. Multiple -s
flags are allowed.