Previous: 09-ModulesPackages.html
**Debugging is twice as hard as writing the code in the first place. **
**Therefore, if you write the code as cleverly as possible, you are, **
**by definition, not smart enough to debug it.**
- Brian W. Kernighan
document.querySelector('video').playbackRate = 1.2
https://en.wikipedia.org/wiki/Troubleshooting
* A basic principle in troubleshooting is to start from the simplest and
most probable possible problems first.
* This is illustrated by the old saying “When you see hoof prints, look
for horses, not zebras”, or to use another maxim, use the KISS
principle
* https://en.wikipedia.org/wiki/KISS_principle
* This principle results in the common complaint about help desks or
manuals, that they sometimes first ask:
* “Is it plugged in, and does that receptacle have power?”
* Always check the simple things first, before calling for help.
* A troubleshooter could check each component in a system one by one,
substituting known good components for each potentially suspect
one.
* However, this process of “serial substitution” can be considered
degenerate when components are substituted without regard to a
hypothesis concerning how their failure could result in the symptoms
being diagnosed.
* Randomly changing your code is even worse!
* Simple and intermediate systems can be characterized by lists or trees
of dependencies among their components or subsystems.
* More complex systems can contain cyclical dependencies or interactions
(feedback loops), and require graph models.
* Such systems are less amenable to “bisection” troubleshooting
techniques.
The general scientific method of abduction and hypothesis testing can
be used to find bugs!
https://en.wikipedia.org/wiki/Abductive_reasoning
https://en.wikipedia.org/wiki/Scientific_method
https://en.wikipedia.org/wiki/Experiment
https://en.wikipedia.org/wiki/Software_bug
A software bug is an error, flaw, failure, or fault in a computer
program or system that causes it to produce an incorrect or unexpected
result, or to behave in unintended ways, often but not always
crashing.
Programming errors come in several forms, and vary by language
type:
https://en.wikipedia.org/wiki/Software_bug#Types
In some compiled languages (C, C++, assembly/asm) attempting to do
low-level stuff:
* Null pointer de-reference.
* Using an uninitialized variable.
* Using an otherwise valid instruction on the wrong data type (see
packed decimal/binary coded decimal).
* Access violations.
* Resource leaks, where a finite system resource (such as memory or file
handles) become exhausted by repeated allocation without release.
* Buffer overflow, in which a program tries to store data past the end
of allocated storage. This may or may not lead to an access violation or
storage violation.
* These are known as security bugs.
* Excessive recursion which, though logically valid, causes stack
overflow.
* Use-after-free error, where a pointer is used after the system has
freed the memory it references.
* Double free error (double delete a memory allocation).
* These can be hard to find, but don’t have to be!
* Languages like https://www.rust-lang.org/ actually pre-check much of
this, functionally reducing or eliminating such issues, in addition to
pre-checking all the features normally checked by a compiled language,
producing a security-safe language!
++++++++++++++++++
Cahoot-10.1
What kind of error is this?
https://mst.instructure.com/courses/58101/quizzes/56033
10-DebuggingTesting/debug_error_00.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def division(numerator: float, denominator: float) -> float:
answer = numerator / denominator
return answer
print(division(37, 0))
++++++++++++++++++
Cahoot-10.2
What kind of error is this?
https://mst.instructure.com/courses/58101/quizzes/56034
10-DebuggingTesting/debug_error_01.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def division(numerator: float, denominator: float) -> float:
answer = (numerator / denominator
return answer
print(division(37, 2))
++++++++++++++++++
Cahoot-10.3
What kind of error is this?
https://mst.instructure.com/courses/58101/quizzes/56035
10-DebuggingTesting/debug_error_02.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def division(numerator: float, denominator: float) -> float:
answer = numerator * denominator
return answer
print(division(37, 2))
++++++++++++++++++
Cahoot-10.4
What kind of error is this?
https://mst.instructure.com/courses/58101/quizzes/56036
10-DebuggingTesting/debug_error_03.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
small_array: List[int] = [0, 1, 2]
small_array[3] = 3
Show this one: $ ipython3 debug_error_03.py
Python itself has error types (called exceptions)
https://docs.python.org/3/library/exceptions.html
10-DebuggingTesting/debug_exceptions.py
(in spyder3)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
while True print('Hello world'):
10 * (1 / 0)
'2' + 2
Print('hello world')
print 'hello world'
print(hello world)
hello world
d = {1: 1, 2: 2}
d[3]
li = [1, 2, 3]
li[4]
f = open('nonexistantfile.txt', 'r')
# Note: check BEFORE the line python whines about:
if 1 == 1:
print('hey'
print('another')
The full tree of python3’s built-in exceptions is:
BaseException
+– SystemExit
+– KeyboardInterrupt
+– GeneratorExit
+– Exception
+– StopIteration
+– StopAsyncIteration
+– ArithmeticError
| +– FloatingPointError
| +– OverflowError
| +– ZeroDivisionError
+– AssertionError
+– AttributeError
+– BufferError
+– EOFError
+– ImportError
| +– ModuleNotFoundError
+– LookupError
| +– IndexError
| +– KeyError
+– MemoryError
+– NameError
| +– UnboundLocalError
+– OSError
| +– BlockingIOError
| +– ChildProcessError
| +– ConnectionError
| | +– BrokenPipeError
| | +– ConnectionAbortedError
| | +– ConnectionRefusedError
| | +– ConnectionResetError
| +– FileExistsError
| +– FileNotFoundError
| +– InterruptedError
| +– IsADirectoryError
| +– NotADirectoryError
| +– PermissionError
| +– ProcessLookupError
| +– TimeoutError
+– ReferenceError
+– RuntimeError
| +– NotImplementedError
| +– RecursionError
+– SyntaxError
| +– IndentationError
| +– TabError
+– SystemError
+– TypeError
+– ValueError
| +– UnicodeError
| +– UnicodeDecodeError
| +– UnicodeEncodeError
| +– UnicodeTranslateError
+– Warning
+– DeprecationWarning
+– PendingDeprecationWarning
+– RuntimeWarning
+– SyntaxWarning
+– UserWarning
+– FutureWarning
+– ImportWarning
+– UnicodeWarning
+– BytesWarning
+– ResourceWarning
Tips for reading exceptions and finding little
errors:
https://realpython.com/python-traceback/ (how to read basic
errors!)
* Read them from the bottom up!
* Read them entirely!!
* Check it out in spyder3 (which uses the ipython3 terminal)
* Run in ipython3 instead of python3
* Syntax errors are often auto-pinpointed just AFTER they actually
occur, so look before it tells you.
* Use auto-highlighting in your IDE, select ([{
by clicking
at the end of the line!
https://en.wikipedia.org/wiki/Debugging
While(bug)
Ask, what is the simplest input that produces the bug?
Identify assumptions that you made about program operation that could be false.
Ask yourself "How does the outcome of this test/change guide me toward finding the problem?"
https://en.wikipedia.org/wiki/Debugging_patterns
Eliminate Noise Bug Pattern
* Isolate and expose a particular bug by eliminating all other noise in
the system.
* This enables you to concentrate on finding the real issue.
Recurring Bug Pattern
* Expose a bug via a unit test.
* Run that unit test as part of a standard build from that moment
on.
* This ensure that the bug will not recur.
Time Specific Bug Pattern
* Expose the bug by writing a continuous test that runs continuously and
fails when an expected error occurs.
* This is useful for transient bugs.
Better known as “tracing” code.
03-IntroPython.html illustrates several good ones for python!
Note: Actually trace the code I give you in these!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
https://en.wikipedia.org/wiki/Heisenbug
:)
When you get to languages like C++, the very act of printing to find a
bug, may hide your bug!
Oh the uncertainty!
https://en.wikipedia.org/wiki/Software_testing
There are many approaches available in software testing.
Reviews, walk-throughs, or inspections are referred to as static
testing, pre-processing, or linting, whereas executing programmed code
with a given set of test cases is referred to as dynamic testing.
https://en.wikipedia.org/wiki/Unit_testing
* Unit testing is a software testing method by which individual units of
source code, sets of one or more computer program modules together with
associated control data, usage procedures, and operating procedures, are
tested to determine whether they are fit for use.
* Unit tests are typically automated tests written and run by software
developers to ensure that a section of an application (known as the
“unit”) meets its design and behaves as intended.
* The unit is typically a function and it’s return value or changes
produced.
* To isolate issues that may arise, each test case should be tested
independently.
Different pre-built testing frameworks exist in many languages:
https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks
For example, in Python:
https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#Python
One really neat one we will cover later this semester is:
https://en.wikipedia.org/wiki/Doctest
https://en.wikipedia.org/wiki/Integration_testing
* Integration testing (sometimes called integration and testing,
abbreviated I&T) is the phase in software testing in which
individual software modules are combined and tested as a group.
* Integration testing is conducted to evaluate the compliance of a
system or component with specified functional requirements.
* It occurs after unit testing and before validation testing.
https://en.wikipedia.org/wiki/Software_verification_and_validation
In software project management, software testing, and software
engineering, verification and validation (V&V) is the process of
checking that a software system meets specifications and that it
fulfills its intended purpose.
It may also be referred to as software quality control.
https://en.wikipedia.org/wiki/Test-driven_development (I suggest reading this one)
10-DebuggingTesting/debug_example1.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
# line 1
swapped: int = 1
a: List[int] = [3, 2, 1]
while swapped == 1: # line 2
swapped = 0 # line 3
for i in range(1, len(a)): # line 4
if a[i - 1] > a[i]: # line 5
a[i] = a[i - 1] # line 6
a[i - 1] = a[i] # line 7
swapped = 1 # line 8
# line 9
# line 10
# line 11
# line 12
Control flow graph for the code
The code contains a bug (i.e., it does not produce correct results for
some inputs).
Given below are the results for 5 test cases including a code trace for
each test case:
Via graph analysis, we determine the bug is in lines 6-9 of the
code.
Namely, the code isn’t swapping a[i] and a[i-1] correctly.
10-DebuggingTesting/debug_example1_fix.py
10-DebuggingTesting/debug_example2.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# line 1
n: int = 10
previous_power: int = 0
m: int = 0
gap: int = 0
total: int = 0
while n > 0: # line 2
m = 0 # line 3
if (n % 2) != 0: # line 4
total = 1 # line 5
# line 6
else:
total = 2 # line 7
# line 8
while (total * 2) <= n: # line 9
total = total * 2 # line 10
m += 1 # line 11
# line 12
for gap in range(1, previous_power - m): # line 13
print(0) # line 14
# line 15
print(1) # line 16
n = n - total # line 17
previous_power = m # line 18
# line 19
for trailingZero in range(m, 0, -1): # line 20
print(0) # line 21
# line 22
print("\n") # line 23
# line 24
Control flow graph for the code:
10-DebuggingTesting/debug_example2_fix.py
Here are some unit tests for the Caesar functions we wrote last
time:
10-DebuggingTesting/debug_unit_tests.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
import random
caesar_encoding: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ "
def key_gen() -> int:
"""
Generates one Caesar key
"""
# Correct
key = random.randint(1, 26)
# Bug
# key = random.randint(0, 27)
return key
def key_gen_test() -> bool:
# What else could we test here?
# What about randomness?
# Will this test always work?
# Does it guarantee correctness?
for c in range(300):
if (key_gen() > 26) or (key_gen() < 1):
print("key_gen_test() failed")
return False
return True
def str_to_num_arr(message: str) -> List[int]:
"""
Translates a string into a Caesar-encoded List
"""
arr: List[int] = []
for character in message:
arr.append(caesar_encoding.find(character.upper()))
return arr
def str_to_num_arr_test() -> bool:
# TODO
return True
def num_arr_to_str(encoded_arr: List[int]) -> str:
"""
Translates a Caesar encoded list back into a string
"""
plaintext: List[str] = []
for counter, encoded_char in enumerate(encoded_arr):
plaintext.append(caesar_encoding[encoded_char])
plaintext_string = "".join(plaintext)
return plaintext_string
def num_arr_to_str_test() -> bool:
# TODO
return True
def translate(encoded_arr: List[int], mode: int, key: int) -> List[int]:
"""
Encrypts or decryps a Caesar-encoded List of ints
"""
translated: List[int] = []
for encoded_char in encoded_arr:
if mode == 1:
# 27 is the symbol set size (# letters in alphabet)
# Note the space added above (bug from last time)!
translated.append((encoded_char + key) % 27)
else:
translated.append((encoded_char - key) % 27)
return translated
def translate_test() -> bool:
# TODO
return True
def encrypt_test() -> bool:
for character in caesar_encoding:
for key in range(1, 26):
# Encrypt
message_arr = str_to_num_arr(character)
message_arr = translate(message_arr, 1, key)
message = num_arr_to_str(message_arr)
# Decrypt
message_arr = str_to_num_arr(message)
message_arr = translate(message_arr, 0, key)
message = num_arr_to_str(message_arr)
# check
if message != character:
print("encrypt_test() failed")
return False
return True
def run_tests() -> bool:
"""
Note: greedy quitting with booleans means that
you should put the more aggregate/complicated tests last,
and the simple tests first.
"""
return (
key_gen_test()
and str_to_num_arr_test()
and num_arr_to_str_test()
and translate_test()
and encrypt_test()
)
# Only runs main if tests pass:
if _name_ == "_main_" and run_tests():
message: str = input("\nEnter your message, in English:\n")
gen_key: str = input("Want to generate a key? (y/n)")
if gen_key == "y":
ok: int = 0
while ok == 0:
key = key_gen()
print("Is the key it ok with you (1-yes, 0-no, make another): ")
ok = int(input())
else:
key = int(input("What is your key (0-25)?"))
print("\nYour Caesar key is: ")
print(key)
print("\n Share this with your partner. Don't tell anyone else\n")
print("\nEnter 1 for encryption, and 0 for decryption: ")
mode: int = int(input())
message_arr = str_to_num_arr(message)
message_arr = translate(message_arr, mode, key)
message = num_arr_to_str(message_arr)
print(message)
Ask in class: How would we complete the # TODO tests above?
You should write tests like this for every single programming assignment!
A child’s game from the below book:
https://en.wikipedia.org/wiki/Hangman_(game)
http://inventwithpython.com/invent4thed/chapter7.html
http://inventwithpython.com/invent4thed/chapter8.html
http://inventwithpython.com/invent4thed/chapter9.html
10-DebuggingTesting/hangman.py
(no typing)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# TODO next time, I'll show you how to auto-type code you download,
# like this I downloaded from one of the free books in this class:
import random
HANGMAN_PICS = [
"""
+---+
|
|
|
===""",
"""
+---+
O |
|
|
===""",
"""
+---+
O |
| |
|
===""",
"""
+---+
O |
/| |
|
===""",
"""
+---+
O |
/|\ |
|
===""",
"""
+---+
O |
/|\ |
/ |
===""",
"""
+---+
O |
/|\ |
/ \ |
===""",
]
words = "ant baboon badger bat bear beaver camel cat clam cobra cougar coyote crow deer dog donkey duck eagle ferret fox frog goat goose hawk lion lizard llama mole monkey moose mouse mule newt otter owl panda parrot pigeon python rabbit ram rat raven rhino salmon seal shark sheep skunk sloth snake spider stork swan tiger toad trout turkey turtle weasel whale wolf wombat zebra".split()
def getRandomWord(wordList):
# This function returns a random string from the passed list of strings.
wordIndex = random.randint(0, len(wordList) - 1)
return wordList[wordIndex]
def displayBoard(missedLetters, correctLetters, secretWord):
print(HANGMAN_PICS[len(missedLetters)])
print()
print("Missed letters:", end=" ")
for letter in missedLetters:
print(letter, end=" ")
print()
blanks = "_" * len(secretWord)
# replace blanks with correctly guessed letters
for i in range(len(secretWord)):
if secretWord[i] in correctLetters:
blanks = blanks[:i] + secretWord[i] + blanks[i + 1 :]
# show the secret word with spaces in between each letter
for letter in blanks:
print(letter, end=" ")
print()
def getGuess(alreadyGuessed):
# Returns the letter the player entered.
# This function makes sure the player entered a single letter,
# and not something else.
while True:
print("Guess a letter.")
guess = input()
guess = guess.lower()
if len(guess) != 1:
print("Please enter a single letter.")
elif guess in alreadyGuessed:
print("You have already guessed that letter. Choose again.")
elif guess not in "abcdefghijklmnopqrstuvwxyz":
print("Please enter a LETTER.")
else:
return guess
def playAgain():
# This function returns True if the player wants to play again;
# otherwise, it returns False.
print("Do you want to play again? (yes or no)")
return input().lower().startswith("y")
def main():
print("H A N G M A N")
missedLetters = ""
correctLetters = ""
secretWord = getRandomWord(words)
gameIsDone = False
while True:
displayBoard(missedLetters, correctLetters, secretWord)
# Let the player enter a letter.
guess = getGuess(missedLetters + correctLetters)
if guess in secretWord:
correctLetters = correctLetters + guess
# Check if the player has won.
foundAllLetters = True
for i in range(len(secretWord)):
if secretWord[i] not in correctLetters:
foundAllLetters = False
break
if foundAllLetters:
print('Yes! The secret word is "' + secretWord + '"! You have won!')
gameIsDone = True
else:
missedLetters = missedLetters + guess
# Check if player has guessed too many times and lost.
if len(missedLetters) == len(HANGMAN_PICS) - 1:
displayBoard(missedLetters, correctLetters, secretWord)
print(
"You have run out of guesses!\nAfter "
+ str(len(missedLetters))
+ " missed guesses and "
+ str(len(correctLetters))
+ ' correct guesses, the word was "'
+ secretWord
+ '"'
)
gameIsDone = True
# Ask the player if they want to play again (but only if the game is done).
if gameIsDone:
if playAgain():
missedLetters = ""
correctLetters = ""
gameIsDone = False
secretWord = getRandomWord(words)
else:
break
if _name_ == "_main_":
main()
How would you design unit tests for the functions in this game?
How would you design integration tests for the main in this game?
How could you check to make sure there are no superficial type-conflicts
(a type of bug)?
Remember:
…that was sarcastic, and in case you didn’t notice, reverse it!
Next: 11-TypeHints.html