From f21fbc7f6232bfb85d31a908fa6bf7278754f08c Mon Sep 17 00:00:00 2001 From: Stockton Jenkins <jsjenkins4@wisc.edu> Date: Thu, 20 Feb 2025 17:14:18 -0600 Subject: [PATCH] debugging-autobadger --- debugging-autobadger.md | 224 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 debugging-autobadger.md diff --git a/debugging-autobadger.md b/debugging-autobadger.md new file mode 100644 index 0000000..45aac22 --- /dev/null +++ b/debugging-autobadger.md @@ -0,0 +1,224 @@ +# Read-only Access + +We've opened up the `autobadger` tool in attempt to make things more visible to you and less of a "black box". We've done this by making the repository *read-only*, meaning you should be able to `git clone` the repo but not `git push` to it. + +To start, navigate to a directory outside of any class project. I'd recommend cloning to the same directory as your projects. + +```bash +git clone https://oauth2:glpat-CSTX_tgpf38eJHyUW213@git.doit.wisc.edu/cdis/cs/courses/cs544/s25/tools/autobadger.git +``` + + +> **NOTE**: if you want to use this method throughout the semester, you'll need to `git pull` to get up-to-date code for each project. + + +Your folder structure should look something like + +``` +some-directory/ + autobadger/ + p1/ + p2/ + ... # other projects +``` + +# Making Changes + +You can change the code inside of `autobadger`. The only files that will be of interest to you are inside the `projects/` directory, i.e. `projects/*.py`). Your changes will be for debugging, i.e. `print()` or `breakpoint()` statements. +#### Using `pip` + +For whatever project you're working on, you will need to *apply* any changes you make using `pip` + +For example, assuming +- I'm working on `p2` +- in my `p2` directory +- and have my `venv` activated + +I would do something like: + +```bash +pip3 install ../autobadger/. +``` + +This would install and replace my local version of `autobadger` . Now when I run + +``` +autobadger --project=p2 +``` + +I will see my changes in effect. + +# Breakpoints + +Since `breakpoint()` is less known and straightforward, I will teach about it here. + +> **NOTE**: It is not required to use `breakpoint()`. You are also welcome to use `print()` instead. `breakpoint()` has a **steeper learning curve**, but may **help you iterate more quickly and save you time** once the basic concepts are well-understood. + +### What is a breakpoint? + +`breakpoint()` is a built-in function in Python and starts the **debugger** at the point where it is called. It allows developers to inspect variables, step through code, and debug interactively. +#### Simple Example: + +```python +# Inside of /path/to/file.py + +def calculate_sum(a, b): + breakpoint() # Debugger starts here + return a + b + + +calculate_sum(3, 5) # execute function +``` + +Adding a `breakpoint()` will pause execution, allowing you to inspect `a` and `b` before proceeding. I would see something like: + +``` +> /path/to/file.py(3)calculate_sum() +-> return a + b +``` + +in the terminal, which displays +1. the next line to be executed `return a + b` +2. `(3)calculate_sum()` tells me the line number and the function name (if applicable) +3. `/path/to/file.py` tells me the current file + +### Navigating the debugger + +While the Python debugger is active, you can use several commands to navigate through your program and investigate. + +- `Variable name`: I can type any variable that is in scope and get it's value. + - Ex: Typing `a` in the previous example would return the *value* of `a` + - **NOTE**: if a variable name also coincides with a command keyword in the debugger, you may need to use `print(<variable_name>)` instead. `b` is one of those commands, so to print the value of `b` to the terminal, I would need to do `print(b)`: +- `Evaluation`: I can also evaluate statements (i.e. add two numbers) + +``` +In [3]: calculate_sum(3, 5) +> <ipython-input-2-443b6e8e0b0a>(3)calculate_sum() +-> return a + b + +(Pdb) print(a) +3 + +(Pdb) print(b) +5 + +(Pdb) print(a + b) +8 +``` + +- `n`: Steps to the next line of my program +- `c`: Continues execution of the program until the next breakpoint, or until the program ends. +- `s`: Steps *into* a function or method call +- `exit`: kills the debugger and ends the program + +# An example + +### Using breakpoints + +Suppose I want to investigate `Q4` for `p2`. I can add `breakpoint()` statements to the Q4 test method for the `ProjectTwoTest` class. + +Navigating to `projects/p2.py` inside of `autobadger`, I find: + +```python +@graded(Q=4, points=10) +def test_simple_http(self) -> int | TestError: + address = self._test_cache_server("-cache-1") + if isinstance(address, TestError): + return address + r = requests.get(f"{address}/lookup/53706") + r.raise_for_status() + result = r.json() + if "addrs" not in result or "source" not in result: + return TestError( + message=f"Result body should be JSON with 'addrs' and 'source' fields, but got {result}.", + earned=5, + ) + return 10 +``` + +> Note: This is Q4 since I have `Q=4` in the decorator. + +**I can edit this method by adding *breakpoints*!** + +```python +@graded(Q=4, points=10) +def test_simple_http(self) -> int | TestError: + breakpoint() + address = self._test_cache_server("-cache-1") + if isinstance(address, TestError): + return address + r = requests.get(f"{address}/lookup/53706") + breakpoint() + r.raise_for_status() + result = r.json() + if "addrs" not in result or "source" not in result: + return TestError( + message=f"Result body should be JSON with 'addrs' and 'source' fields, but got {result}.", + earned=5, + ) + return 10 +``` + +Now, after I update with `pip` as mentioned above, I can run `autobadger --project=p2` and get: + +``` +> /Users/.../p2.py(103)test_simple_http() +-> address = self._test_cache_server("-cache-1") +``` + +Note that in this situation, typing `address` would give me an error cause it **not yet defined**: + +``` +(Pdb) address +*** NameError: name 'address' is not defined +``` +###### Using `n` (next line) + +`address` defined on the *next line*. So, I use the `n` command to step! + +``` +(Pdb) n +> /Users/.../p2.py(104)test_simple_http() +-> if isinstance(address, TestError): + +(Pdb) address +'http://localhost:64879' +``` +###### Using `s` (step into) + +I could have also used `s` to *step into* `self._test_cache_server(...)` if I had wanted to investigate further: + +``` +> /Users/.../p2.py(103)test_simple_http() +-> address = self._test_cache_server("-cache-1") + +(Pdb) s +--Call-- +> /Users/.../p2.py(118)_test_cache_server() +-> def _test_cache_server(self, server_suffix: str) -> str | TestError: + # Now in a new method — _test_cache_server + +(Pdb) n +> /Users/.../p2.py(119)_test_cache_server() +-> cache_server = [c for c in self.containers if c["Name"].endswith(server_suffix)] + +``` +###### Using `c` (continue) + +I can also *continue* till the next breakpoint, which is quite convenient if you don't need to step over every line of code: + +``` +> /Users/.../p2.py(103)test_simple_http() +-> address = self._test_cache_server("-cache-1") + +(Pdb) c +> /Users/.../p2.py(108)test_simple_http() +-> r.raise_for_status() + +(Pdb) print(r.json()) +{'addrs': [...], 'error': None, 'source': '...'} +``` + +Using `c` jumped from line `103` to line `108`, where I had my two breakpoints defined. + +> **NOTE**: using `c` again would continue the Python program till the end of its execution since I have no other `breakpoint()` statements \ No newline at end of file -- GitLab