Skip to content
Snippets Groups Projects
debugging-autobadger.md 6.79 KiB
Newer Older
Stockton Jenkins's avatar
Stockton Jenkins committed
# 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