Debugging

During the lecture, you learned some strategies to debug a program. Time to apply them! You’ll keep a logbook, like they did for “First actual case of bug being found”.

We want to find the total the kinetic energy of the a group of bodies, from a file that details their mass and speed.

You might need to review the equation and unit convertions formulas.

Instructions

  1. Create a new branch (called exercise-debugging).
  2. Create a new folder where you put:
    1. The file bodies.txt1 with mass and velocity of each body.
    2. The script kinetic.py2 that attempts to calculate total Kinectic Energy of the.
    3. A new file debugging-logbook.txt where you will describe the debugging strategies you use.
  3. Try to fix the program. Describe your approach on the debugging-logbook.txt file.
    1. Remember the learnings from error messages and logging.
    2. Try to solve one problem at a time, commit the fix and move to the next.
  4. Open a Merge request in your repository titled “Exercise: Debugging Kinectic Energy”.
    1. Assign it yourself.
    2. Add the lecturer (Manuel Reis) as reviewer.
Want help? How to run the script

It shows the usage message if you don’t provide a filename. You might need to mark it as executable chmod u+x kinectic.py

python kinetic.py
CRITICAL:__main__:Usage: kinetic.py filename [--debug]

There is already some debug logging but you might need to add more or modify them.

python kinetic.py bodies.txt --debug
DEBUG:__main__:Parsing 1000g
DEBUG:__main__:Parsed value '1000' and unit 'fee/s'
ERROR:__main__:Failed processing 1000g@10km/h 
DEBUG:__main__:Parsing 2kg
DEBUG:__main__:Parsed value '2k' and unit 'fee/s'
ERROR:__main__:Failed processing 2kg@3m/s 
DEBUG:__main__:Parsing 500g
DEBUG:__main__:Parsed value '500' and unit 'fee/s'
ERROR:__main__:Failed processing 500g@5Km/h 
DEBUG:__main__:Parsing 5kg
DEBUG:__main__:Parsed value '5k' and unit 'fee/s'
ERROR:__main__:Failed processing 5kg@1feet/s 
DEBUG:__main__:Parsing 𝟙𝟘𝟘𝟘g
DEBUG:__main__:Parsed value '𝟙𝟘𝟘𝟘' and unit 'fee/s'
ERROR:__main__:Failed processing 𝟙𝟘𝟘𝟘g@1km/h 
DEBUG:__main__:Parsing 730 g 
DEBUG:__main__:Parsed value '730' and unit 'fee/s'
ERROR:__main__:Failed processing 730 g @ 1.1 m/s 
DEBUG:__main__:Parsing 1Kg 
DEBUG:__main__:Parsed value '1k' and unit 'fee/s'
ERROR:__main__:Failed processing 1Kg @2m    /s 
Total energy: 0 J
Tip

There are at least 4 bugs in the code. The correct result for the input file is ≈ 16.05 J.

Footnotes

  1. Here is the input file!

    bodies.txt
    1000g@10km/h
    2kg@3m/s
    500g@5Km/h
    5kg@1feet/s
    𝟙𝟘𝟘𝟘g@1km/h
    730g @ 1.1 m/s
    1Kg@2m  /s
    ↩︎
  2. Here is the buggy script that computes the sum of Kinetic energies from the bodies present in the input file. The input file should contain one line per body, and it should be of the from <mass><unit>@<velocity><unit>. Note that whitespaces (" ", "\t") are ignored and letter case is insensitive.

    kinetic.py
    #!/usr/bin/env python
    import logging
    from sys import argv, exit
    
    logging.basicConfig()
    logger = logging.getLogger(__name__)
    
    def kinetic_energy(mass, velocity):
        """Kinetic Energy formula 1/2 * m * v ** 2"""
        return 0.5 * mass * velocity
    
    def total_energy(samples):
        total = 0
        for mass, velocity in samples:
            ke = kinetic_energy(mass, velocity)
            logger.debug("1/2 * %s * %s ** 2 = %s", mass, velocity, ke)
            total += ke
    
        return total
    
    def parse_units(input_str: str) -> tuple[int, str]:
        """Splits units from the end of string, returns truncated input and parsed unit"""
        logger.debug("Parsing %s", input_str)
        # Ignoring case and removing whitespaces
        input_str = input_str.lower().replace(" ", "").replace("\t", "")
        value, unit = None, None
        for unit in (
            "kg",
            "g",
            "km/s",
            "m/s",
            "km/h",
            "fee/s",
        ):
            if input_str.endswith(unit):
                value = input_str.replace(unit, "")
                # We're done, we found the unit
    
        logger.debug("Parsed value '%s' and unit '%s'", value, unit)
        return float(value), unit
    
    def convert_units(value, unit):
        if unit == "g":
            return value / 1000
        elif unit == "kg":
            return value
        elif unit == "km/h":
            return value * 0.2 # Aproximation
        elif unit == "m/s":
            return value
        elif unit == "feet/s":
            return value * 0.3048
        raise ValueError("Unsupported unit type: %s", unit)
    
    def read_data(file_path):
        samples = []
    
        with open(file_path, "r") as f:
            for line in f:
                try:
                    parts = line.strip().split("@")
                    mass_value, unit = parse_units(parts[0])
                    mass = convert_units(mass_value, unit)
                    logger.info("Convertion Mass %s %s to %s Kg", mass_value, unit, mass)
    
                    velocity_value, unit = parse_units(parts[1])
                    velocity = convert_units(velocity_value, unit)
                    logger.info(
                        "Convertion Velocity %s %s to %s m/s", velocity_value, unit, mass
                    )
                    samples.append((mass, velocity))
                except Exception:
                    logger.error("Failed processing %s ", line.strip())
                    continue
    
        return samples
    
    if __name__ == "__main__":
        if len(argv) < 2:
            logger.critical("Usage: %s filename [--debug]", argv[0])
            exit(1)
        if "--debug" in argv[1:]:
            logger.setLevel(logging.DEBUG)
        filename = argv[1]
        data = read_data(filename)
        print("Total energy:", total_energy(data), "J")
    ↩︎

Reuse