{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"# Code Coverage\n",
"\n",
"In the [previous chapter](Fuzzer.ipynb), we introduced _basic fuzzing_ – that is, generating random inputs to test programs. How do we measure the effectiveness of these tests? One way would be to check the number (and seriousness) of bugs found; but if bugs are scarce, we need a _proxy for the likelihood of a test to uncover a bug._ In this chapter, we introduce the concept of *code coverage*, measuring which parts of a program are actually executed during a test run. Measuring such coverage is also crucial for test generators that attempt to cover as much code as possible."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.012698Z",
"iopub.status.busy": "2024-01-18T17:14:15.012053Z",
"iopub.status.idle": "2024-01-18T17:14:15.080809Z",
"shell.execute_reply": "2024-01-18T17:14:15.080491Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from bookutils import YouTubeVideo\n",
"YouTubeVideo('8HxW8j9287A')"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Prerequisites**\n",
"\n",
"* You need some understanding of how a program is executed.\n",
"* You should have learned about basic fuzzing in the [previous chapter](Fuzzer.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
},
"tags": []
},
"source": [
"## Synopsis\n",
"\n",
"\n",
"To [use the code provided in this chapter](Importing.ipynb), write\n",
"\n",
"```python\n",
">>> from fuzzingbook.Coverage import \n",
"```\n",
"\n",
"and then make use of the following features.\n",
"\n",
"\n",
"This chapter introduces a `Coverage` class allowing you to measure coverage for Python programs. Within the context of this book, we use coverage information to guide fuzzing towards uncovered locations.\n",
"\n",
"The typical usage of the `Coverage` class is in conjunction with a `with` clause:\n",
"\n",
"```python\n",
">>> with Coverage() as cov:\n",
">>> cgi_decode(\"a+b\")\n",
"```\n",
"Printing out a coverage object shows the covered functions, with non-covered lines prefixed with `#`:\n",
"\n",
"```python\n",
">>> print(cov)\n",
"# 1 def cgi_decode(s: str) -> str:\n",
"# 2 \"\"\"Decode the CGI-encoded string `s`:\n",
"# 3 * replace '+' by ' '\n",
"# 4 * replace \"%xx\" by the character with hex number xx.\n",
"# 5 Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n",
"# 6 \n",
"# 7 # Mapping of hex digits to their integer values\n",
" 8 hex_values = {\n",
" 9 '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n",
" 10 '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n",
" 11 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n",
" 12 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n",
"# 13 }\n",
"# 14 \n",
" 15 t = \"\"\n",
" 16 i = 0\n",
" 17 while i < len(s):\n",
" 18 c = s[i]\n",
" 19 if c == '+':\n",
" 20 t += ' '\n",
" 21 elif c == '%':\n",
"# 22 digit_high, digit_low = s[i + 1], s[i + 2]\n",
"# 23 i += 2\n",
"# 24 if digit_high in hex_values and digit_low in hex_values:\n",
"# 25 v = hex_values[digit_high] * 16 + hex_values[digit_low]\n",
"# 26 t += chr(v)\n",
"# 27 else:\n",
"# 28 raise ValueError(\"Invalid encoding\")\n",
"# 29 else:\n",
" 30 t += c\n",
" 31 i += 1\n",
" 32 return t\n",
"\n",
"\n",
"```\n",
"The `trace()` method returns the _trace_ – that is, the list of locations executed in order. Each location comes as a pair (`function name`, `line`).\n",
"\n",
"```python\n",
">>> cov.trace()\n",
"[('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 12),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 12),\n",
" ('cgi_decode', 8),\n",
" ('cgi_decode', 15),\n",
" ('cgi_decode', 16),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 18),\n",
" ('cgi_decode', 19),\n",
" ('cgi_decode', 21),\n",
" ('cgi_decode', 30),\n",
" ('cgi_decode', 31),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 18),\n",
" ('cgi_decode', 19),\n",
" ('cgi_decode', 20),\n",
" ('cgi_decode', 31),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 18),\n",
" ('cgi_decode', 19),\n",
" ('cgi_decode', 21),\n",
" ('cgi_decode', 30),\n",
" ('cgi_decode', 31),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 32)]\n",
"```\n",
"The `coverage()` method returns the _coverage_, that is, the set of locations in the trace executed at least once:\n",
"\n",
"```python\n",
">>> cov.coverage()\n",
"{('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 12),\n",
" ('cgi_decode', 15),\n",
" ('cgi_decode', 16),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 18),\n",
" ('cgi_decode', 19),\n",
" ('cgi_decode', 20),\n",
" ('cgi_decode', 21),\n",
" ('cgi_decode', 30),\n",
" ('cgi_decode', 31),\n",
" ('cgi_decode', 32)}\n",
"```\n",
"Coverage sets can be subject to set operations, such as _intersection_ (which locations are covered in multiple executions) and _difference_ (which locations are covered in run _a_, but not _b_).\n",
"\n",
"The chapter also discusses how to obtain such coverage from C programs.\n",
"\n",
"![](PICS/Coverage-synopsis-1.svg)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.102805Z",
"iopub.status.busy": "2024-01-18T17:14:15.102643Z",
"iopub.status.idle": "2024-01-18T17:14:15.104757Z",
"shell.execute_reply": "2024-01-18T17:14:15.104455Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import bookutils.setup"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.106721Z",
"iopub.status.busy": "2024-01-18T17:14:15.106562Z",
"iopub.status.idle": "2024-01-18T17:14:15.108312Z",
"shell.execute_reply": "2024-01-18T17:14:15.108033Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"# ignore\n",
"from typing import Any, Optional, Callable, List, Type, Set, Tuple"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## A CGI Decoder\n",
"\n",
"We start by introducing a simple Python function that decodes a CGI-encoded string. CGI encoding is used in URLs (i.e., Web addresses) to encode characters that would be invalid in a URL, such as blanks and certain punctuation:\n",
"\n",
"* Blanks are replaced by `'+'`\n",
"* Other invalid characters are replaced by '`%xx`', where `xx` is the two-digit hexadecimal equivalent.\n",
"\n",
"In CGI encoding, the string `\"Hello, world!\"` would thus become `\"Hello%2c+world%21\"` where `2c` and `21` are the hexadecimal equivalents of `','` and `'!'`, respectively.\n",
"\n",
"The function `cgi_decode()` takes such an encoded string and decodes it back to its original form. Our implementation replicates the code from \\cite{Pezze2008}. (It even includes its bugs – but we won't reveal them at this point.)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.109988Z",
"iopub.status.busy": "2024-01-18T17:14:15.109877Z",
"iopub.status.idle": "2024-01-18T17:14:15.113197Z",
"shell.execute_reply": "2024-01-18T17:14:15.112928Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"def cgi_decode(s: str) -> str:\n",
" \"\"\"Decode the CGI-encoded string `s`:\n",
" * replace '+' by ' '\n",
" * replace \"%xx\" by the character with hex number xx.\n",
" Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n",
"\n",
" # Mapping of hex digits to their integer values\n",
" hex_values = {\n",
" '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n",
" '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n",
" 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n",
" 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n",
" }\n",
"\n",
" t = \"\"\n",
" i = 0\n",
" while i < len(s):\n",
" c = s[i]\n",
" if c == '+':\n",
" t += ' '\n",
" elif c == '%':\n",
" digit_high, digit_low = s[i + 1], s[i + 2]\n",
" i += 2\n",
" if digit_high in hex_values and digit_low in hex_values:\n",
" v = hex_values[digit_high] * 16 + hex_values[digit_low]\n",
" t += chr(v)\n",
" else:\n",
" raise ValueError(\"Invalid encoding\")\n",
" else:\n",
" t += c\n",
" i += 1\n",
" return t"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Here is an example of how `cgi_decode()` works:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.114649Z",
"iopub.status.busy": "2024-01-18T17:14:15.114564Z",
"iopub.status.idle": "2024-01-18T17:14:15.116576Z",
"shell.execute_reply": "2024-01-18T17:14:15.116313Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'Hello world'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cgi_decode(\"Hello+world\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"If we want to systematically test `cgi_decode()`, how would we proceed?"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The testing literature distinguishes two ways of deriving tests: _Black-box testing_ and _White-box testing._"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Black-Box Testing\n",
"\n",
"The idea of *black-box testing* is to derive tests from the _specification_. In the above case, we thus would have to test `cgi_decode()` by the features specified and documented, including\n",
"\n",
"* testing for correct replacement of `'+'`;\n",
"* testing for correct replacement of `\"%xx\"`;\n",
"* testing for non-replacement of other characters; and\n",
"* testing for recognition of illegal inputs.\n",
"\n",
"Here are four assertions (tests) that cover these four features. We can see that they all pass:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.118071Z",
"iopub.status.busy": "2024-01-18T17:14:15.117992Z",
"iopub.status.idle": "2024-01-18T17:14:15.119734Z",
"shell.execute_reply": "2024-01-18T17:14:15.119490Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"assert cgi_decode('+') == ' '\n",
"assert cgi_decode('%20') == ' '\n",
"assert cgi_decode('abc') == 'abc'\n",
"\n",
"try:\n",
" cgi_decode('%?a')\n",
" assert False\n",
"except ValueError:\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The advantage of black-box testing is that it finds errors in the _specified_ behavior. It is independent of a given implementation, and thus allows creating tests even before implementation. The downside is that _implemented_ behavior typically covers more ground than _specified_ behavior, and thus tests based on specification alone typically do not cover all implementation details."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## White-Box Testing\n",
"\n",
"In contrast to black-box testing, *white-box testing* derives tests from the _implementation_, notably the internal structure. White-Box testing is closely tied to the concept of _covering_ structural features of the code. If a statement in the code is not executed during testing, for instance, this means that an error in this statement cannot be triggered either. White-Box testing thus introduces a number of *coverage criteria* that have to be fulfilled before the test can be said to be sufficient. The most frequently used coverage criteria are\n",
"\n",
"* *Statement coverage* – each statement in the code must be executed by at least one test input.\n",
"* *Branch coverage* – each branch in the code must be taken by at least one test input. (This translates to each `if` and `while` decision once being true, and once being false.)\n",
"\n",
"Besides these, there are far more coverage criteria, including sequences of branches taken, loop iterations taken (zero, one, many), data flows between variable definitions and usages, and many more; \\cite{Pezze2008} has a great overview."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us consider `cgi_decode()`, above, and reason what we have to do such that each statement of the code is executed at least once. We'd have to cover\n",
"\n",
"* The block following `if c == '+'`\n",
"* The two blocks following `if c == '%'` (one for valid input, one for invalid)\n",
"* The final `else` case for all other characters.\n",
"\n",
"This results in the same conditions as with black-box testing, above; again, the assertions above indeed would cover every statement in the code. Such a correspondence is actually pretty common, since programmers tend to implement different behaviors in different code locations; and thus, covering these locations will lead to test cases that cover the different (specified) behaviors.\n",
"\n",
"The advantage of white-box testing is that it finds errors in _implemented_ behavior. It can be conducted even in cases where the specification does not provide sufficient details; actually, it helps in identifying (and thus specifying) corner cases in the specification. The downside is that it may miss _non-implemented_ behavior: If some specified functionality is missing, white-box testing will not find it."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Tracing Executions\n",
"\n",
"One nice feature of white-box testing is that one can actually automatically assess whether some program feature was covered. To this end, one _instruments_ the execution of the program such that during execution, a special functionality keeps track of which code was executed. After testing, this information can be passed to the programmer, who can then focus on writing tests that cover the yet uncovered code."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"In most programming languages, it is rather difficult to set up programs such that one can trace their execution. Not so in Python. The function `sys.settrace(f)` allows defining a *tracing function* `f()` that is called for each and every line executed. Even better, it gets access to the current function and its name, current variable contents, and more. It is thus an ideal tool for *dynamic analysis* – that is, the analysis of what actually happens during an execution."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To illustrate how this works, let us again look into a specific execution of `cgi_decode()`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.121528Z",
"iopub.status.busy": "2024-01-18T17:14:15.121435Z",
"iopub.status.idle": "2024-01-18T17:14:15.123680Z",
"shell.execute_reply": "2024-01-18T17:14:15.123415Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'a b'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cgi_decode(\"a+b\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To track how the execution proceeds through `cgi_decode()`, we make use of `sys.settrace()`. First, we define the _tracing function_ that will be called for each line. It has three parameters: \n",
"\n",
"* The `frame` parameter gets you the current _frame_, allowing access to the current location and variables:\n",
" * `frame.f_code` is the currently executed code with `frame.f_code.co_name` being the function name;\n",
" * `frame.f_lineno` holds the current line number; and\n",
" * `frame.f_locals` holds the current local variables and arguments.\n",
"* The `event` parameter is a string with values including `\"line\"` (a new line has been reached) or `\"call\"` (a function is being called).\n",
"* The `arg` parameter is an additional _argument_ for some events; for `\"return\"` events, for instance, `arg` holds the value being returned."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We use the tracing function for simply reporting the current line executed, which we access through the `frame` argument."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.125248Z",
"iopub.status.busy": "2024-01-18T17:14:15.125167Z",
"iopub.status.idle": "2024-01-18T17:14:15.126784Z",
"shell.execute_reply": "2024-01-18T17:14:15.126547Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from types import FrameType, TracebackType"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.128208Z",
"iopub.status.busy": "2024-01-18T17:14:15.128126Z",
"iopub.status.idle": "2024-01-18T17:14:15.129769Z",
"shell.execute_reply": "2024-01-18T17:14:15.129474Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"coverage = []"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.131303Z",
"iopub.status.busy": "2024-01-18T17:14:15.131222Z",
"iopub.status.idle": "2024-01-18T17:14:15.133131Z",
"shell.execute_reply": "2024-01-18T17:14:15.132870Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def traceit(frame: FrameType, event: str, arg: Any) -> Optional[Callable]:\n",
" \"\"\"Trace program execution. To be passed to sys.settrace().\"\"\"\n",
" if event == 'line':\n",
" global coverage\n",
" function_name = frame.f_code.co_name\n",
" lineno = frame.f_lineno\n",
" coverage.append(lineno)\n",
"\n",
" return traceit"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can switch tracing on and off with `sys.settrace()`:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.134618Z",
"iopub.status.busy": "2024-01-18T17:14:15.134538Z",
"iopub.status.idle": "2024-01-18T17:14:15.136118Z",
"shell.execute_reply": "2024-01-18T17:14:15.135806Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import sys"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.137561Z",
"iopub.status.busy": "2024-01-18T17:14:15.137473Z",
"iopub.status.idle": "2024-01-18T17:14:15.139390Z",
"shell.execute_reply": "2024-01-18T17:14:15.139090Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def cgi_decode_traced(s: str) -> None:\n",
" global coverage\n",
" coverage = []\n",
" sys.settrace(traceit) # Turn on\n",
" cgi_decode(s)\n",
" sys.settrace(None) # Turn off"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"When we compute `cgi_decode(\"a+b\")`, we can now see how the execution progresses through `cgi_decode()`. After the initialization of `hex_values`, `t`, and `i`, we see that the `while` loop is taken three times – one for every character in the input."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.141115Z",
"iopub.status.busy": "2024-01-18T17:14:15.141027Z",
"iopub.status.idle": "2024-01-18T17:14:15.142799Z",
"shell.execute_reply": "2024-01-18T17:14:15.142536Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 11, 8, 11, 8, 11, 8, 11, 8, 11, 8, 11, 8, 12, 8, 12, 8, 15, 16, 17, 18, 19, 21, 30, 31, 17, 18, 19, 20, 31, 17, 18, 19, 21, 30, 31, 17, 32]\n"
]
}
],
"source": [
"cgi_decode_traced(\"a+b\")\n",
"print(coverage)"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Which lines are these, actually? To this end, we get the source code of `cgi_decode_code` and encode it into an array `cgi_decode_lines`, which we will then annotate with coverage information. First, let us get the source code of `cgi_encode`:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.144320Z",
"iopub.status.busy": "2024-01-18T17:14:15.144235Z",
"iopub.status.idle": "2024-01-18T17:14:15.145887Z",
"shell.execute_reply": "2024-01-18T17:14:15.145616Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import inspect"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.147347Z",
"iopub.status.busy": "2024-01-18T17:14:15.147266Z",
"iopub.status.idle": "2024-01-18T17:14:15.149631Z",
"shell.execute_reply": "2024-01-18T17:14:15.149383Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"cgi_decode_code = inspect.getsource(cgi_decode)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"`cgi_decode_code` is a string holding the source code. We can print it out with Python syntax highlighting:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.151188Z",
"iopub.status.busy": "2024-01-18T17:14:15.151106Z",
"iopub.status.idle": "2024-01-18T17:14:15.152687Z",
"shell.execute_reply": "2024-01-18T17:14:15.152441Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from bookutils import print_content, print_file"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.154175Z",
"iopub.status.busy": "2024-01-18T17:14:15.154086Z",
"iopub.status.idle": "2024-01-18T17:14:15.230618Z",
"shell.execute_reply": "2024-01-18T17:14:15.230345Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m \u001b[32mcgi_decode\u001b[39;49;00m(s: \u001b[36mstr\u001b[39;49;00m) -> \u001b[36mstr\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
"\u001b[37m \u001b[39;49;00m\u001b[33m\"\"\"Decode the CGI-encoded string `s`:\u001b[39;49;00m\n",
"\u001b[33m * replace '+' by ' '\u001b[39;49;00m\n",
"\u001b[33m * replace \"%xx\" by the character with hex number xx.\u001b[39;49;00m\n",
"\u001b[33m Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"\u001b[37m\u001b[39;49;00m\n",
" \u001b[37m# Mapping of hex digits to their integer values\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" hex_v...\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(cgi_decode_code[:300] + \"...\", \".py\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Using `splitlines()`, we split the code into an array of lines, indexed by line number."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.232210Z",
"iopub.status.busy": "2024-01-18T17:14:15.232125Z",
"iopub.status.idle": "2024-01-18T17:14:15.233837Z",
"shell.execute_reply": "2024-01-18T17:14:15.233608Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"cgi_decode_lines = [\"\"] + cgi_decode_code.splitlines()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"`cgi_decode_lines[L]` is line L of the source code."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.235309Z",
"iopub.status.busy": "2024-01-18T17:14:15.235204Z",
"iopub.status.idle": "2024-01-18T17:14:15.237209Z",
"shell.execute_reply": "2024-01-18T17:14:15.236972Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'def cgi_decode(s: str) -> str:'"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cgi_decode_lines[1]"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We see that the first line (9) executed is actually the initialization of `hex_values`..."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.238765Z",
"iopub.status.busy": "2024-01-18T17:14:15.238651Z",
"iopub.status.idle": "2024-01-18T17:14:15.240934Z",
"shell.execute_reply": "2024-01-18T17:14:15.240643Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[\" '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\",\n",
" \" '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\",\n",
" \" 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\",\n",
" \" 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\"]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cgi_decode_lines[9:13]"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"... followed by the initialization of `t`:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.242490Z",
"iopub.status.busy": "2024-01-18T17:14:15.242374Z",
"iopub.status.idle": "2024-01-18T17:14:15.244343Z",
"shell.execute_reply": "2024-01-18T17:14:15.244083Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"' t = \"\"'"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cgi_decode_lines[15]"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To see which lines actually have been covered at least once, we can convert `coverage` into a set:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.245856Z",
"iopub.status.busy": "2024-01-18T17:14:15.245729Z",
"iopub.status.idle": "2024-01-18T17:14:15.247418Z",
"shell.execute_reply": "2024-01-18T17:14:15.247186Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{32, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 20, 21, 30, 31}\n"
]
}
],
"source": [
"covered_lines = set(coverage)\n",
"print(covered_lines)"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us print out the full code, annotating lines not covered with `#`.\n",
"The idea of such an annotation is to direct developer's attention to the non-covered lines."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:15.248849Z",
"iopub.status.busy": "2024-01-18T17:14:15.248751Z",
"iopub.status.idle": "2024-01-18T17:14:16.140400Z",
"shell.execute_reply": "2024-01-18T17:14:16.140099Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# 1 \u001b[34mdef\u001b[39;49;00m \u001b[32mcgi_decode\u001b[39;49;00m(s: \u001b[36mstr\u001b[39;49;00m) -> \u001b[36mstr\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
"# 2 \u001b[33m\"\"\"\u001b[39;49;00m\u001b[33mDecode the CGI-encoded string `s`:\u001b[39;49;00m\u001b[33m\u001b[39;49;00m\n",
"# 3 * replace \u001b[33m'\u001b[39;49;00m\u001b[33m+\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m by \u001b[33m'\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"# 4 * replace \u001b[33m\"\u001b[39;49;00m\u001b[33m%x\u001b[39;49;00m\u001b[33mx\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m by the character \u001b[34mwith\u001b[39;49;00m \u001b[36mhex\u001b[39;49;00m number xx.\u001b[37m\u001b[39;49;00m\n",
"# 5 Return the decoded string. Raise \u001b[04m\u001b[91m`\u001b[39;49;00m\u001b[36mValueError\u001b[39;49;00m\u001b[04m\u001b[91m`\u001b[39;49;00m \u001b[34mfor\u001b[39;49;00m invalid inputs.\u001b[33m\"\"\"\u001b[39;49;00m\u001b[33m\u001b[39;49;00m\n",
"# 6 \u001b[37m\u001b[39;49;00m\n",
"# 7 \u001b[37m# Mapping of hex digits to their integer values\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" 8 hex_values = {\u001b[37m\u001b[39;49;00m\n",
" 9 \u001b[33m'\u001b[39;49;00m\u001b[33m0\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m0\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m1\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m1\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m2\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m2\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m3\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m3\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m4\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m4\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n",
" 10 \u001b[33m'\u001b[39;49;00m\u001b[33m5\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m5\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m6\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m6\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m7\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m7\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m8\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m8\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m9\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m9\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n",
" 11 \u001b[33m'\u001b[39;49;00m\u001b[33ma\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m10\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mb\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m11\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mc\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m12\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33md\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m13\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33me\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m14\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mf\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m15\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n",
" 12 \u001b[33m'\u001b[39;49;00m\u001b[33mA\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m10\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mB\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m11\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mC\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m12\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mD\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m13\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mE\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m14\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mF\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m15\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n",
"# 13 }\u001b[37m\u001b[39;49;00m\n",
"# 14 \u001b[37m\u001b[39;49;00m\n",
" 15 t = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" 16 i = \u001b[34m0\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" 17 \u001b[34mwhile\u001b[39;49;00m i < \u001b[36mlen\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n",
" 18 c = s[i]\u001b[37m\u001b[39;49;00m\n",
" 19 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m+\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
" 20 t += \u001b[33m'\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" 21 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m%\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
"# 22 digit_high, digit_low = s[i + \u001b[34m1\u001b[39;49;00m], s[i + \u001b[34m2\u001b[39;49;00m]\u001b[37m\u001b[39;49;00m\n",
"# 23 i += \u001b[34m2\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"# 24 \u001b[34mif\u001b[39;49;00m digit_high \u001b[35min\u001b[39;49;00m hex_values \u001b[35mand\u001b[39;49;00m digit_low \u001b[35min\u001b[39;49;00m hex_values:\u001b[37m\u001b[39;49;00m\n",
"# 25 v = hex_values[digit_high] * \u001b[34m16\u001b[39;49;00m + hex_values[digit_low]\u001b[37m\u001b[39;49;00m\n",
"# 26 t += \u001b[36mchr\u001b[39;49;00m(v)\u001b[37m\u001b[39;49;00m\n",
"# 27 \u001b[34melse\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
"# 28 \u001b[34mraise\u001b[39;49;00m \u001b[36mValueError\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mInvalid encoding\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\u001b[37m\u001b[39;49;00m\n",
"# 29 \u001b[34melse\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
" 30 t += c\u001b[37m\u001b[39;49;00m\n",
" 31 i += \u001b[34m1\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" 32 \u001b[34mreturn\u001b[39;49;00m t\u001b[37m\u001b[39;49;00m\n"
]
}
],
"source": [
"for lineno in range(1, len(cgi_decode_lines)):\n",
" if lineno not in covered_lines:\n",
" print(\"# \", end=\"\")\n",
" else:\n",
" print(\" \", end=\"\")\n",
" print(\"%2d \" % lineno, end=\"\")\n",
" print_content(cgi_decode_lines[lineno], '.py')\n",
" print()"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We see that a number of lines (notably comments) have not been executed (marked with `#`), simply because they are not executable.\n",
"However, we also see that the lines under `elif c == '%'` have _not_ been executed yet.\n",
"If `\"a+b\"` were our only test case so far, this missing coverage would now encourage us to create another test case that actually covers these `#`-marked lines."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## A Coverage Class\n",
"\n",
"In this book, we will make use of coverage again and again – to _measure_ the effectiveness of different test generation techniques, but also to _guide_ test generation towards code coverage. Our previous implementation with a global `coverage` variable is a bit cumbersome for that. We therefore implement some functionality that will help us measure coverage easily."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The key idea of getting coverage is to make use of the Python `with` statement. The general form\n",
"\n",
"```python\n",
"with OBJECT [as VARIABLE]:\n",
" BODY\n",
"```\n",
"\n",
"executes `BODY` with `OBJECT` being defined (and stored in `VARIABLE`). The interesting thing is that at the beginning and end of `BODY`, the special methods `OBJECT.__enter__()` and `OBJECT.__exit__()` are automatically invoked; even if `BODY` raises an exception. This allows us to define a `Coverage` object where `Coverage.__enter__()` automatically turns on tracing and `Coverage.__exit__()` automatically turns off tracing again. After tracing, we can make use of special methods to access the coverage. This is what this looks like during usage:\n",
"\n",
"```python\n",
"with Coverage() as cov:\n",
" function_to_be_traced()\n",
"c = cov.coverage()\n",
"```\n",
"\n",
"Here, tracing is automatically turned on during `function_to_be_traced()` and turned off again after the `with` block; afterwards, we can access the set of lines executed."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Here's the full implementation with all its bells and whistles. You don't have to get everything; it suffices that you know how to use it:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.142313Z",
"iopub.status.busy": "2024-01-18T17:14:16.142198Z",
"iopub.status.idle": "2024-01-18T17:14:16.143912Z",
"shell.execute_reply": "2024-01-18T17:14:16.143646Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"Location = Tuple[str, int]"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.145560Z",
"iopub.status.busy": "2024-01-18T17:14:16.145456Z",
"iopub.status.idle": "2024-01-18T17:14:16.150054Z",
"shell.execute_reply": "2024-01-18T17:14:16.149816Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Coverage:\n",
" \"\"\"Track coverage within a `with` block. Use as\n",
" ```\n",
" with Coverage() as cov:\n",
" function_to_be_traced()\n",
" c = cov.coverage()\n",
" ```\n",
" \"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" \"\"\"Constructor\"\"\"\n",
" self._trace: List[Location] = []\n",
"\n",
" # Trace function\n",
" def traceit(self, frame: FrameType, event: str, arg: Any) -> Optional[Callable]:\n",
" \"\"\"Tracing function. To be overloaded in subclasses.\"\"\"\n",
" if self.original_trace_function is not None:\n",
" self.original_trace_function(frame, event, arg)\n",
"\n",
" if event == \"line\":\n",
" function_name = frame.f_code.co_name\n",
" lineno = frame.f_lineno\n",
" if function_name != '__exit__': # avoid tracing ourselves:\n",
" self._trace.append((function_name, lineno))\n",
"\n",
" return self.traceit\n",
"\n",
" def __enter__(self) -> Any:\n",
" \"\"\"Start of `with` block. Turn on tracing.\"\"\"\n",
" self.original_trace_function = sys.gettrace()\n",
" sys.settrace(self.traceit)\n",
" return self\n",
"\n",
" def __exit__(self, exc_type: Type, exc_value: BaseException,\n",
" tb: TracebackType) -> Optional[bool]:\n",
" \"\"\"End of `with` block. Turn off tracing.\"\"\"\n",
" sys.settrace(self.original_trace_function)\n",
" return None # default: pass all exceptions\n",
"\n",
" def trace(self) -> List[Location]:\n",
" \"\"\"The list of executed lines, as (function_name, line_number) pairs\"\"\"\n",
" return self._trace\n",
"\n",
" def coverage(self) -> Set[Location]:\n",
" \"\"\"The set of executed lines, as (function_name, line_number) pairs\"\"\"\n",
" return set(self.trace())\n",
"\n",
" def function_names(self) -> Set[str]:\n",
" \"\"\"The set of function names seen\"\"\"\n",
" return set(function_name for (function_name, line_number) in self.coverage())\n",
"\n",
" def __repr__(self) -> str:\n",
" \"\"\"Return a string representation of this object.\n",
" Show covered (and uncovered) program code\"\"\"\n",
" t = \"\"\n",
" for function_name in self.function_names():\n",
" # Similar code as in the example above\n",
" try:\n",
" fun = eval(function_name)\n",
" except Exception as exc:\n",
" t += f\"Skipping {function_name}: {exc}\"\n",
" continue\n",
"\n",
" source_lines, start_line_number = inspect.getsourcelines(fun)\n",
" for lineno in range(start_line_number, start_line_number + len(source_lines)):\n",
" if (function_name, lineno) not in self.trace():\n",
" t += \"# \"\n",
" else:\n",
" t += \" \"\n",
" t += \"%2d \" % lineno\n",
" t += source_lines[lineno - start_line_number]\n",
"\n",
" return t"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us put this to use:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.151548Z",
"iopub.status.busy": "2024-01-18T17:14:16.151465Z",
"iopub.status.idle": "2024-01-18T17:14:16.153353Z",
"shell.execute_reply": "2024-01-18T17:14:16.153106Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{('cgi_decode', 20), ('cgi_decode', 10), ('cgi_decode', 32), ('cgi_decode', 16), ('cgi_decode', 12), ('cgi_decode', 19), ('cgi_decode', 9), ('cgi_decode', 15), ('cgi_decode', 31), ('cgi_decode', 18), ('cgi_decode', 8), ('cgi_decode', 21), ('cgi_decode', 11), ('cgi_decode', 17), ('cgi_decode', 30)}\n"
]
}
],
"source": [
"with Coverage() as cov:\n",
" cgi_decode(\"a+b\")\n",
"\n",
"print(cov.coverage())"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"As you can see, the `Coverage()` class not only keeps track of lines executed, but also of function names. This is useful if you have a program that spans multiple files."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For interactive use, we can simply print the coverage object, and obtain a listing of the code, again with non-covered lines marked as `#`."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.154940Z",
"iopub.status.busy": "2024-01-18T17:14:16.154853Z",
"iopub.status.idle": "2024-01-18T17:14:16.157538Z",
"shell.execute_reply": "2024-01-18T17:14:16.157265Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# 1 def cgi_decode(s: str) -> str:\n",
"# 2 \"\"\"Decode the CGI-encoded string `s`:\n",
"# 3 * replace '+' by ' '\n",
"# 4 * replace \"%xx\" by the character with hex number xx.\n",
"# 5 Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n",
"# 6 \n",
"# 7 # Mapping of hex digits to their integer values\n",
" 8 hex_values = {\n",
" 9 '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n",
" 10 '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n",
" 11 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n",
" 12 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n",
"# 13 }\n",
"# 14 \n",
" 15 t = \"\"\n",
" 16 i = 0\n",
" 17 while i < len(s):\n",
" 18 c = s[i]\n",
" 19 if c == '+':\n",
" 20 t += ' '\n",
" 21 elif c == '%':\n",
"# 22 digit_high, digit_low = s[i + 1], s[i + 2]\n",
"# 23 i += 2\n",
"# 24 if digit_high in hex_values and digit_low in hex_values:\n",
"# 25 v = hex_values[digit_high] * 16 + hex_values[digit_low]\n",
"# 26 t += chr(v)\n",
"# 27 else:\n",
"# 28 raise ValueError(\"Invalid encoding\")\n",
"# 29 else:\n",
" 30 t += c\n",
" 31 i += 1\n",
" 32 return t\n",
"\n"
]
}
],
"source": [
"print(cov)"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Comparing Coverage\n",
"\n",
"Since we represent coverage as a set of executed lines, we can also apply _set operations_ on these. For instance, we can find out which lines are covered by individual test cases, but not others:"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.159119Z",
"iopub.status.busy": "2024-01-18T17:14:16.159010Z",
"iopub.status.idle": "2024-01-18T17:14:16.161501Z",
"shell.execute_reply": "2024-01-18T17:14:16.161266Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('cgi_decode', 20)}"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with Coverage() as cov_plus:\n",
" cgi_decode(\"a+b\")\n",
"with Coverage() as cov_standard:\n",
" cgi_decode(\"abc\")\n",
"\n",
"cov_plus.coverage() - cov_standard.coverage()"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"This is the single line in the code that is executed only in the `'a+b'` input."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can also compare sets to find out which lines still need to be covered. Let us define `cov_max` as the maximum coverage we can achieve. (Here, we do this by executing the \"good\" test cases we already have. In practice, one would statically analyze code structure, which we introduce in [the chapter on symbolic testing](SymbolicFuzzer.ipynb).)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.163047Z",
"iopub.status.busy": "2024-01-18T17:14:16.162940Z",
"iopub.status.idle": "2024-01-18T17:14:16.164814Z",
"shell.execute_reply": "2024-01-18T17:14:16.164584Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"with Coverage() as cov_max:\n",
" cgi_decode('+')\n",
" cgi_decode('%20')\n",
" cgi_decode('abc')\n",
" try:\n",
" cgi_decode('%?a')\n",
" except Exception:\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Then, we can easily see which lines are _not_ yet covered by a test case:"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.166290Z",
"iopub.status.busy": "2024-01-18T17:14:16.166185Z",
"iopub.status.idle": "2024-01-18T17:14:16.168176Z",
"shell.execute_reply": "2024-01-18T17:14:16.167936Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('cgi_decode', 22),\n",
" ('cgi_decode', 23),\n",
" ('cgi_decode', 24),\n",
" ('cgi_decode', 25),\n",
" ('cgi_decode', 26),\n",
" ('cgi_decode', 28)}"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cov_max.coverage() - cov_plus.coverage()"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Again, these would be the lines handling `\"%xx\"`, which we have not yet had in the input."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Coverage of Basic Fuzzing\n",
"\n",
"We can now use our coverage tracing to assess the _effectiveness_ of testing methods – in particular, of course, test _generation_ methods. Our challenge is to achieve maximum coverage in `cgi_decode()` just with random inputs. In principle, we should _eventually_ get there, as eventually, we will have produced every possible string in the universe – but exactly how long is this? To this end, let us run just one fuzzing iteration on `cgi_decode()`:"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.169660Z",
"iopub.status.busy": "2024-01-18T17:14:16.169562Z",
"iopub.status.idle": "2024-01-18T17:14:16.234522Z",
"shell.execute_reply": "2024-01-18T17:14:16.234205Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Fuzzer import fuzzer"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.236302Z",
"iopub.status.busy": "2024-01-18T17:14:16.236214Z",
"iopub.status.idle": "2024-01-18T17:14:16.238473Z",
"shell.execute_reply": "2024-01-18T17:14:16.238226Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'!7#%\"*#0=)$;%6*;>638:*>80\"=>(/*:-(2<4 !:5*6856&?\"\"11<7+%<%7,4.8,*+&,,$,.\"'"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sample = fuzzer()\n",
"sample"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Here's the invocation and the coverage we achieve. We wrap `cgi_decode()` in a `try...except` block such that we can ignore `ValueError` exceptions raised by illegal `%xx` formats."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.240134Z",
"iopub.status.busy": "2024-01-18T17:14:16.240019Z",
"iopub.status.idle": "2024-01-18T17:14:16.242591Z",
"shell.execute_reply": "2024-01-18T17:14:16.242328Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('cgi_decode', 8),\n",
" ('cgi_decode', 9),\n",
" ('cgi_decode', 10),\n",
" ('cgi_decode', 11),\n",
" ('cgi_decode', 12),\n",
" ('cgi_decode', 15),\n",
" ('cgi_decode', 16),\n",
" ('cgi_decode', 17),\n",
" ('cgi_decode', 18),\n",
" ('cgi_decode', 19),\n",
" ('cgi_decode', 21),\n",
" ('cgi_decode', 22),\n",
" ('cgi_decode', 23),\n",
" ('cgi_decode', 24),\n",
" ('cgi_decode', 28),\n",
" ('cgi_decode', 30),\n",
" ('cgi_decode', 31)}"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with Coverage() as cov_fuzz:\n",
" try:\n",
" cgi_decode(sample)\n",
" except:\n",
" pass\n",
"cov_fuzz.coverage()"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Is this already the maximum coverage? Apparently, there are still lines missing:"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.244068Z",
"iopub.status.busy": "2024-01-18T17:14:16.243966Z",
"iopub.status.idle": "2024-01-18T17:14:16.246095Z",
"shell.execute_reply": "2024-01-18T17:14:16.245870Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('cgi_decode', 20),\n",
" ('cgi_decode', 25),\n",
" ('cgi_decode', 26),\n",
" ('cgi_decode', 32)}"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cov_max.coverage() - cov_fuzz.coverage()"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us try again, increasing coverage over 100 random inputs. We use an array `cumulative_coverage` to store the coverage achieved over time; `cumulative_coverage[0]` is the total number of lines covered after input 1, \n",
"`cumulative_coverage[1]` is the number of lines covered after inputs 1–2, and so on."
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.247552Z",
"iopub.status.busy": "2024-01-18T17:14:16.247451Z",
"iopub.status.idle": "2024-01-18T17:14:16.248937Z",
"shell.execute_reply": "2024-01-18T17:14:16.248704Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"trials = 100"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.250342Z",
"iopub.status.busy": "2024-01-18T17:14:16.250267Z",
"iopub.status.idle": "2024-01-18T17:14:16.252554Z",
"shell.execute_reply": "2024-01-18T17:14:16.252325Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def population_coverage(population: List[str], function: Callable) \\\n",
" -> Tuple[Set[Location], List[int]]:\n",
" cumulative_coverage: List[int] = []\n",
" all_coverage: Set[Location] = set()\n",
"\n",
" for s in population:\n",
" with Coverage() as cov:\n",
" try:\n",
" function(s)\n",
" except:\n",
" pass\n",
" all_coverage |= cov.coverage()\n",
" cumulative_coverage.append(len(all_coverage))\n",
"\n",
" return all_coverage, cumulative_coverage"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us create a hundred inputs to determine how coverage increases:"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.254015Z",
"iopub.status.busy": "2024-01-18T17:14:16.253933Z",
"iopub.status.idle": "2024-01-18T17:14:16.255748Z",
"shell.execute_reply": "2024-01-18T17:14:16.255515Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def hundred_inputs() -> List[str]:\n",
" population = []\n",
" for i in range(trials):\n",
" population.append(fuzzer())\n",
" return population"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Here's how the coverage increases with each input:"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.257156Z",
"iopub.status.busy": "2024-01-18T17:14:16.257075Z",
"iopub.status.idle": "2024-01-18T17:14:16.272471Z",
"shell.execute_reply": "2024-01-18T17:14:16.272184Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"all_coverage, cumulative_coverage = \\\n",
" population_coverage(hundred_inputs(), cgi_decode)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.274117Z",
"iopub.status.busy": "2024-01-18T17:14:16.274029Z",
"iopub.status.idle": "2024-01-18T17:14:16.549045Z",
"shell.execute_reply": "2024-01-18T17:14:16.548748Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.551189Z",
"iopub.status.busy": "2024-01-18T17:14:16.551007Z",
"iopub.status.idle": "2024-01-18T17:14:16.552800Z",
"shell.execute_reply": "2024-01-18T17:14:16.552549Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"button": false,
"execution": {
"iopub.execute_input": "2024-01-18T17:14:16.554378Z",
"iopub.status.busy": "2024-01-18T17:14:16.554264Z",
"iopub.status.idle": "2024-01-18T17:14:16.655109Z",
"shell.execute_reply": "2024-01-18T17:14:16.654767Z"
},
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Text(0, 0.5, 'lines covered')"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABYCElEQVR4nO3deVhUZfsH8O9hG3YQRZBYXAsVRUNDcE9S0dckTU19wzVbMFNSCyu3TFwySzO3t8Qylyy3zFRyQVFBQSk1xSV3BVcYQAVknt8f/ubkyAwyOgsO3891zXUxZ55zzn0Oy3PzbEcSQggQERERVSJW5g6AiIiIyNSYABEREVGlwwSIiIiIKh0mQERERFTpMAEiIiKiSocJEBEREVU6TICIiIio0mECRERERJUOEyAiIiKqdJgAEVUAmzdvRpMmTWBvbw9JkpCTk2OS87Zr1w7t2rUzyLFq1qyJgQMHGuRYxvCk8V24cAH29vbYs2ePvO21115D7969DRCdbjt37oQkSdi5c2e5y/78889GjcnYJEnCxIkTzR2GzJC/J1RxMAEiDadPn8abb76J2rVrw97eHq6urmjZsiW++uor3Llzx9zhWaQbN26gd+/ecHBwwLx58/DDDz/AycnJ3GHRQyZPnozQ0FC0bNlS3vbBBx/gl19+wZ9//mnSWJYvX44vv/zSpOekioPff8OwMXcAVHH89ttv6NWrFxQKBaKjoxEUFISioiIkJydjzJgxOHr0KBYtWmTuMC3OgQMHkJeXh08//RQREREmPffWrVtNer6n1bVr17B06VIsXbpUY3vTpk3RrFkzzJo1C99//71Rzt2mTRvcuXMHdnZ28rbly5fjyJEjGDlypFHOSZoq2u8Jv/+GwQSIAABnzpzBa6+9hoCAAGzfvh01atSQP4uJicGpU6fw22+/mTFC3W7fvg1HR0dzh/HYrl69CgBwd3c3+bkfrFRJt2XLlsHGxgbdunUr9Vnv3r0xYcIEfPPNN3B2djb4ua2srGBvb2/w45ZXQUFBpW+R5O+JZWIXGAEAZsyYgfz8fHz77bcayY9a3bp18d5778nv7927h08//RR16tSBQqFAzZo1MW7cOBQWFspl/vOf/6B27dpazxcWFoZmzZppbFu2bBlCQkLg4OAADw8PvPbaa7hw4YJGmXbt2iEoKAjp6elo06YNHB0dMW7cOADA+vXr0bVrV/j4+EChUKBOnTr49NNPUVJSUur88+bNQ+3ateHg4IAXXngBu3fv1trPX1hYiAkTJqBu3bpQKBTw8/PD2LFjNa6zLKtXr5avqVq1avjvf/+LS5cuaVzPgAEDAADNmzeHJEmPHKdy6dIlDBkyRL7OWrVq4e2330ZRUZFc5q+//kLbtm3h4OAAX19fTJkyBUuWLIEkSTh79qzG+fUd2yCEwJQpU+Dr6wtHR0e0b98eR48e1Vo2JycHI0eOhJ+fHxQKBerWrYvp06dDpVJplFOpVPjqq6/QqFEj2Nvbw9PTE507d0ZaWppcpjw/c8aKb926dQgNDdWa4Lz00ksoKChAYmJimfetR48eeP755zW2devWDZIkYcOGDfK21NRUSJKE33//HUDpMUDt2rXDb7/9hnPnzkGSJEiShJo1a5a6n5999hl8fX1hb2+PDh064NSpU2XGBwATJ06EJEn4+++/0a9fP1SpUgWtWrUCcP9nauDAgXL3uLe3NwYPHowbN25oPcapU6cwcOBAuLu7w83NDYMGDcLt27c1yhYWFmLUqFHw9PSEi4sLXn75ZVy8eFFrbIcOHUJkZCRcXV3h7OyMDh06ICUlRaNMQkICJElCcnIyRowYAU9PT7i7u+PNN99EUVERcnJyEB0djSpVqqBKlSoYO3YshBCPvC8P/56ovyc//fTTI+/zg3+zwsPD4eDggFq1amHBggVaY3/w9/PBc5X3+z937lw0bNgQjo6OqFKlCpo1a4bly5c/8horI7YAEQDg119/Re3atREeHl6u8kOHDsXSpUvx6quv4v3330dqairi4+Nx7NgxrF27FgDQp08fREdH48CBA2jevLm877lz55CSkoKZM2fK2z777DN88skn6N27N4YOHYpr165h7ty5aNOmDQ4dOqTROnLjxg1ERkbitddew3//+194eXkBuP8HxNnZGbGxsXB2dsb27dsxfvx4KJVKjXPNnz8fw4cPR+vWrTFq1CicPXsWUVFRqFKlCnx9feVyKpUKL7/8MpKTkzFs2DDUr18fhw8fxuzZs3HixAmsW7euzHuUkJCAQYMGoXnz5oiPj0d2dja++uor7NmzR76mjz76CM899xwWLVqEyZMno1atWqhTp47OY16+fBkvvPACcnJyMGzYMAQGBuLSpUv4+eefcfv2bdjZ2eHSpUto3749JElC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\n",
"text/plain": [
"