diff --git a/line_profiler/_line_profiler.pyx b/line_profiler/_line_profiler.pyx index 15510317..a6bb5a20 100644 --- a/line_profiler/_line_profiler.pyx +++ b/line_profiler/_line_profiler.pyx @@ -471,7 +471,19 @@ cdef class LineProfiler: @property def c_last_time(self): - return (self._c_last_time)[threading.get_ident()] + """ + Raises: + KeyError + If no profiling data is available on the current thread. + """ + try: + return (self._c_last_time)[threading.get_ident()] + except KeyError as e: + # We haven't actually profiled anything yet + raise (KeyError('No profiling data on the current thread ' + '(`threading.get_ident()` = ' + f'{threading.get_ident()})') + .with_traceback(e.__traceback__)) from None @property def code_map(self): @@ -500,13 +512,12 @@ cdef class LineProfiler: line_profiler 4.0 no longer directly maintains last_time, but this will construct something similar for backwards compatibility. """ - c_last_time = (self._c_last_time)[threading.get_ident()] - code_hash_map = self.code_hash_map + c_last_time = self.c_last_time py_last_time = {} - for code, code_hashes in code_hash_map.items(): - for code_hash in code_hashes: - if code_hash in c_last_time: - py_last_time[code] = c_last_time[code_hash] + for code in self.code_hash_map: + block_hash = hash(code.co_code) + if block_hash in c_last_time: + py_last_time[code] = c_last_time[block_hash] return py_last_time diff --git a/tests/test_line_profiler.py b/tests/test_line_profiler.py index 954c77b1..8e15ff7b 100644 --- a/tests/test_line_profiler.py +++ b/tests/test_line_profiler.py @@ -80,6 +80,39 @@ def test_init(): } +def test_last_time(): + """ + Test that `LineProfiler.c_last_time` and `LineProfiler.last_time` + are consistent. + """ + prof = LineProfiler() + with pytest.raises(KeyError, match='[Nn]o profiling data'): + prof.c_last_time + + def get_last_time(prof, *, c=False): + try: + return getattr(prof, 'c_last_time' if c else 'last_time') + except KeyError: + return {} + + @prof + def func(): + return (get_last_time(prof, c=True).copy(), + get_last_time(prof).copy()) + + # These are always empty outside a profiling context + # (hence the need of the above function to capture the transient + # values) + assert not get_last_time(prof, c=True) + assert not get_last_time(prof) + # Inside `func()`, both should get an entry therefor + clt, lt = func() + assert not get_last_time(prof, c=True) + assert not get_last_time(prof) + assert set(clt) == {hash(func.__wrapped__.__code__.co_code)} + assert set(lt) == {func.__wrapped__.__code__} + + def test_enable_disable(): lp = LineProfiler() assert lp.enable_count == 0