Comet shows the crew her first move() from last night. "It is shorter," she says proudly. "I left out the grid check. Pip just goes."
Wren reads it twice. "What does the evidence say?" he asks. "Let's run it next to a rock and next to an edge, and look at where Pip ends up."
Nova projects the field with Pip sitting on top of a rock. "How can I help?" she asks. "Compare the runs with your real rover.py. Look for the quiet ones."
Comet laughs at the picture. "Fine, that is data. Lead programmer, which of these runs should worry us most?"
Here is move() in rover_nocheck.py, a copy of rover.py without the is_open check. The real rover.py is never changed.
Common security issues in programs include missing bounds checks and poor input validation. Today you see both.
def move(self, grid):
"""Step forward one cell if it is open. Gives back True or False."""
d_row, d_col = STEPS[self._heading]
new_row = self._row + d_row
new_col = self._col + d_col
if self._battery < self.step_use():
self._log.append("battery low")
return False
self._row = new_row
self._col = new_col
self._battery -= self.step_use()
self._log.append(f"moved to ({new_row}, {new_col})")
return True # nocheck_drive.py
from field_data import FIELD
from grid import FieldGrid
from rover_nocheck import Rover
grid = FieldGrid(FIELD)
first = Rover("Pip", 1, 0)
first.move(grid)
print(first.status())
print("Cell under Pip:", grid.cell(first.get_row(), first.get_col()))
second = Rover("Pip")
second.turn_left()
second.move(grid)
print(second.status())
print("Cell under Pip:", grid.cell(second.get_row(), second.get_col())) Pip at (1, 1) facing E, battery 95 Cell under Pip: # Pip at (-1, 0) facing N, battery 95 Cell under Pip: .
Now a third Pip starts at (4, 0), turns to face S, and drives off the bottom edge:
# nocheck_edge.py
from field_data import FIELD
from grid import FieldGrid
from rover_nocheck import Rover
grid = FieldGrid(FIELD)
third = Rover("Pip", 4, 0)
third.turn_right()
third.move(grid)
print(third.status())
print("Cell under Pip:", grid.cell(third.get_row(), third.get_col())) Pip at (5, 0) facing S, battery 95
Traceback (most recent call last):
File "nocheck_edge.py", line 11, in <module>
print("Cell under Pip:", grid.cell(third.get_row(), third.get_col()))
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "grid.py", line 23, in cell
return self._cells[row][col]
~~~~~~~~~~~^^^^^
IndexError: list index out of range A run-time error happens while the program runs; IndexError is one. It is loud: the program stops and names the line.
A logic error makes a program behave wrongly. Pip on a rock is quiet: nothing stops, and only testing finds it.
| Run | What happened | Kind of error |
|---|---|---|
| first Pip | stood on the rock at (1, 1) | logic error (quiet) |
| second Pip | at row -1, reading the last row | logic error (quiet) |
| third Pip | cell(5, 0) stopped with IndexError | run-time error (loud) |
| Statement | True or false? |
|---|---|
| Without the check, Pip can stand on a rock. | ? |
| Every missing bounds check makes the program stop. | ? |
| An IndexError is a run-time error. | ? |
| The real rover.py was changed for these runs. | ? |
Input needs checks too. This program trusts a typed start row:
# start_nocheck.py
from rover import Rover
start = int(input("Start row: "))
pip = Rover("Pip", start, 0)
print(pip.status()) Pip now starts off the field, and nothing complains. This version checks the text first:
# start_check.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
grid = FieldGrid(FIELD)
text = input("Start row: ")
if text.isdigit() and grid.in_bounds(int(text), 0):
pip = Rover("Pip", int(text), 0)
print(pip.status())
else:
print("Start row must be 0 to", grid.height() - 1) You found quiet bugs on purpose, which is how real testers work. Tomorrow you write the tests.