pythonpytest

How to get begining line number and end line number of a test method in python?


I'm currently trying to get the first and last line numbers of all test methods in a python file.

For example:

The file test_lift.py tests the file lift.py.

test_lift.py

1 import random
2
3 from src.lift import Lift
4 from flaky import flaky
5
6
7 def test_upwards():
8         tester = Lift()
9         tester.upwards()
10        assert 1 == tester.curr_floor
11
12 def test_downwards():
13        tester = Lift()
14        tester.curr_floor = 1
15        tester.downwards()
16        assert 0 == tester.curr_floor
17        tester.downwards()
18        assert 0 == tester.curr_floor
19
...

I now want to get the first and last line number for each test method in test_lift.py, in this case for example:

test_upwards, 7, 10

test_downwards, 12, 18

I've already tried using the conftest.py, but without any success. Maybe I'm overlooking something? The solution doesn't necessarily have to be in python. If anybody knows how to solve this problem by parsing the files I'm fine with that.


Solution

  • Or, without any additional modules (but some Python internals):

    >>> def thing():
    ...  a = 5
    ...  b = a + 4
    ...  return a + b * 2
    ... 
    >>> thing.__code__.co_firstlineno  # self-explanatory
    1
    >>> list(thing.__code__.co_lnotab)
    [0, 1, 4, 1, 8, 1]
    >>> thing.__code__.co_firstlineno + sum(line_increment for i, line_increment in enumerate(thing.__code__.co_lnotab) if i % 2)
    4
    

    So there you have it: the function spans from line 1 (thing.__code__.co_firstlineno) to line 4.

    The dis module proves this (the numbers in the first column are line numbers):

    >>> import dis
    >>> dis.dis(thing)
      2           0 LOAD_CONST               1 (5)
                  2 STORE_FAST               0 (a)
    
      3           4 LOAD_FAST                0 (a)
                  6 LOAD_CONST               2 (4)
                  8 BINARY_ADD
                 10 STORE_FAST               1 (b)
    
      4          12 LOAD_FAST                0 (a)
                 14 LOAD_FAST                1 (b)
                 16 LOAD_CONST               3 (2)
                 18 BINARY_MULTIPLY
                 20 BINARY_ADD
                 22 RETURN_VALUE
    

    Notice how the last number is 4 - that's the number the last line of the function.

    More about the structure of co_lnotab can be found here and here.


    Test program

    def span_of_function(func):
      start = func.__code__.co_firstlineno
      end = start + sum(line_increment for i, line_increment in enumerate(func.__code__.co_lnotab) if i % 2)
      
      return start, end
    
    def hello(name):
      print(f'Hello, {name}!')
      return 5
    
    print(span_of_function(span_of_function))
    print(span_of_function(hello))
    

    Output:

    $ python3 test.py
    (1, 5)
    (7, 9)