Linked handout: 9. Making it work: debugging

Rubber duck debugging in a hotel room in Darmstadt @ ESA 2019 Space debris conference

Making it work: debugging

    Today we will cover

  • Finding bugs before they happen
  • Finding bugs after they happen
  • Finding bugs while they happen

Making it work: debugging

So what is a "bug"

On September 9, 1945, U.S. Navy officer Grace Hopper found a moth between some relays on the Harvard Mark II computer

And thus and the term "bug in the computer" caught on

Making it work: debugging

So what is a "bug"

Today it means "unwanted behaviour"

Making it work: debugging

  • Finding bugs before they happen
  • Finding bugs after they happen
  • Finding bugs while they happen

Making it work: debugging

  • Finding bugs before they happen

The most common methods

  • Reading the code
  • Static code analyzers
  • Tests

Making it work: debugging

  • Finding bugs before they happen
  • Reading the code
  • You reviewing your code
  • Others reviewing your code

Making it work: debugging

  • Finding bugs before they happen
  • Reading the code
  • You reviewing your code
  • Others reviewing your code

Making it work: debugging

  • Finding bugs before they happen
  • Reading the code
  • You reviewing your code
  • Others reviewing your code

Make a routine out of it!

We are practicing this now with the homework

Making it work: debugging

  • Finding bugs before they happen
  • Static code analyzers
  • mypy (Python)
  • flake8 / ruff (Python)
  • Any LSP server (clangd, pylsp, ...)
  • Any compiler (clang, gcc, rustc) !
  • ikos (C/C++)
  • CodeQL (C/C++, Java, JavaScript, Python)
  • [commercial] Cppcheck (C/C++)
  • [commercial] Sonar (C/C++, Java, JavaScript, Python)

github analysis-tools-dev has a long list!

Making it work: debugging

  • Finding bugs before they happen
  • Static code analyzers

We are already doing this (right?!) after our tooling lecture!

Making it work: debugging

  • Finding bugs before they happen
  • Tests
  • Unit testing
  • Integration testing
  • End-to-end testing
  • Compatibility testing
  • Performance testing
  • Stress testing
  • Security testing
  • ...

Practical examples

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Unit testing

                    
                        class TestAnomalies(unittest.TestCase):
                            def test_eccentric_true_inverse(self):
                                nu0 = 1.2345
                                e = 0.5
                                nu = kep.eccentric_to_true(kep.true_to_eccentric(nu0, e), e)
                                self.assertAlmostEqual(nu0, nu)
                    
                

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Integration testing

                    
                        class TestKeplerSolver(unittest.TestCase):
                            @pytest.mark.slow
                            def test_laguerre_solve_kepler(self):
                                E = np.linspace(0.0, 2.0*np.pi, num=300, dtype=np.float64)
                                e = np.linspace(0, 0.99, num=500, dtype=np.float64)

                                for eit in e:
                                    for Eit in E:
                                        M = kep.eccentric_to_mean(Eit, eit)

                                        E0 = kep.kepler_guess(M, eit)
                                        E_calc, it = kep.laguerre_solve_kepler(E0, M, eit, tol=1e-12)
                                        fun_err = np.abs(M - E_calc + eit*np.sin(E_calc))

                                        nt.assert_almost_equal(Eit, E_calc, decimal = 1e-9)
                                        assert fun_err < 1e-11
                    
                

Making it work: debugging

  • Finding bugs before they happen
  • Tests

End-to-end testing

                    
                        
                    
                

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Compatibility testing

                    
                        test_python_3_10:
                          image: python:3.10
                          before_script:
                            - pip install virtualenv
                            - virtualenv venv
                            - source venv/bin/activate
                            - pip install .
                          script:
                            - pip install pytest
                            - pytest
                          rules:
                            - if: $CI_COMMIT_BRANCH == "main"
                    
                

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Compatibility testing

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Performance testing

Next lecture!

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Stress testing

                    
                        class TestServer(unittest.TestCase):
                            ...
                            def test_connect_client(self):
                                clients = []
                                for ind in range(10_000):
                                    clients.append(
                                        self.launch_client_request_thread()
                                    )
                                for client in clients:
                                    self.close_and_validate(client)
                    
                

Making it work: debugging

  • Finding bugs before they happen
  • Tests

Security testing

                    
                        danielk@IRF081-danielk ~/g/pyorb (main)> grype dir:.
                         ✔ Vulnerability DB                [updated]  
                         ✔ Indexed file system  . 
                         ✔ Cataloged contents   cdb4ee2aea69cc6a83331bbe96dc2caa9a299d21329efb0336fc02a82e1839a8 
                           ├── ✔ Packages                        [1 packages]  
                           ├── ✔ Executables                     [0 executables]  
                           ├── ✔ File metadata                   [2 locations]  
                           └── ✔ File digests                    [2 files]  
                         ✔ Scanned for vulnerabilities     [0 vulnerability matches]  
                           ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible
                           └── by status:   0 fixed, 0 not-fixed, 0 ignored 
                        No vulnerabilities found
                    
                

For example grype (podcast in handouts)

Making it work: debugging

  • Finding bugs before they happen
  • Tests

We are already doing this (right?!) after our testing homework and tooling lecture!

Making it work: debugging

  • Finding bugs before they happen
  • Finding bugs after they happen
  • Finding bugs while they happen

Making it work: debugging

  • Finding bugs after they happen

The most common methods

  • Logging
  • Examining program output
    (plots, data, real world actions, ...)
  • Asserts and exceptions

Making it work: debugging

  • Finding bugs after they happen

The most common methods

  • Logging
  • Next lecture!

  • Examining program output
    (plots, data, real world actions, ...)
  • This is the default - no need to cover here

  • Asserts and exceptions
  • A quick second look (we covered syntax before)

Making it work: debugging

  • Finding bugs after they happen
  • Asserts and exceptions
                    
                        def stack(logger, file: Path, output_folder=Path | None, duration=1.0, clobber=False):
                            ...
                            if frame_cnt == 0:
                                raise EmptyVideoFile("Empty video file, cannot produce stack")
                            ...
                    
                
                    
                        def worker(file, output_folder, duration, clobber):
                            logger = logging.getLogger("allsky7station.processing.stack")
                            logger.info(f"Processing {file.name}")
                            try:
                                stack(logger, file, output_folder=output_folder, duration=duration, clobber=clobber)
                            except EmptyVideoFile:
                                logger.warning(f"Empty video-file {file}, not writing output stack")
                            except FileExistsError:
                                logger.warning(f"{file} exists, not making stack")
                    
                

Making it work: debugging

  • Finding bugs after they happen
  • Asserts and exceptions
                    
                        
                    
                

Making it work: debugging

  • Finding bugs after they happen
  • Asserts and exceptions
                    
                        python -O my_code.py
                    
                

python cmdline -O: remove asserts and __debug__ code

Making it work: debugging

  • Finding bugs after they happen
  • Asserts and exceptions

mypy understands "type narrowing"

                    
                        def function(arg: Any):
                            assert isinstance(arg, int)
                            reveal_type(arg)  # Revealed type: "builtins.int"
                    
                

Making it work: debugging

  • Finding bugs after they happen
  • Asserts and exceptions

Assertion heavy coding! (video in handouts)

                    
                        # val should never be nan here
                    
                

vs

                    
                        assert not np.isnan(val), "this should never happen?!"
                    
                

Making it work: debugging

  • Finding bugs after they happen
  • Bonus method: post-mortem
                    
                        # automatically enter post-mortem if abnormal exit
                        python -m pdb -c continue my_code.py
                    
                

It will enter debugger with current state at point of error
pdb.post_mortem

Making it work: debugging

  • Finding bugs before they happen
  • Finding bugs after they happen
  • Finding bugs while they happen

Making it work: debugging

  • Finding bugs while they happen

The most common methods

  • Dynamic/runtime analyzers
  • Debuggers

Making it work: debugging

  • Finding bugs while they happen
  • Dynamic/runtime analyzers
  • tracemalloc (python)
  • valgrind (C/C++)
  • ...

Making it work: debugging

  • Finding bugs while they happen
  • Dynamic/runtime analyzers
  • tracemalloc (python)
                    
                        import tracemalloc
                        tracemalloc.start()

                        # ... code

                        snapshot = tracemalloc.take_snapshot()
                        top_stats = snapshot.statistics('lineno')
                        print("[ Top 10 ]")
                        for stat in top_stats[:10]:
                            print(stat)
                    
                

Making it work: debugging

  • Finding bugs while they happen
  • Debuggers

Practical example! Go to handout