aboutsummaryrefslogtreecommitdiff
path: root/tests/errors/context.test.py
blob: fff9e0941df6a8bb8e8aeb7df2b10b6ce7287a60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""Tests for the caller-source-location capture helpers in :mod:`edify.errors.context`."""

from unittest import mock

from edify.errors.context import (
    CallerContext,
    _context_for_frame,
    capture_caller_context,
)


class _FakeCode:
    """A stand-in for ``types.CodeType`` used to force specific ``co_positions`` behaviour."""

    def __init__(self, filename: str, positions: list[tuple]) -> None:
        self.co_filename = filename
        self._positions = list(positions)

    def co_positions(self):
        return iter(self._positions)


class _FakeFrame:
    """A stand-in for ``types.FrameType`` shaped for :func:`_context_for_frame`."""

    def __init__(
        self,
        filename: str,
        f_lineno: int,
        f_lasti: int,
        positions: list[tuple],
    ) -> None:
        self.f_code = _FakeCode(filename, positions)
        self.f_lineno = f_lineno
        self.f_lasti = f_lasti


def test_capture_caller_context_returns_none_when_every_frame_is_inside_edify():
    with mock.patch("edify.errors.context.sys._getframe", return_value=None):
        assert capture_caller_context() is None


def test_context_for_frame_uses_lineno_fallback_when_instruction_index_out_of_range():
    frame = _FakeFrame(
        filename="/tmp/fake.py",
        f_lineno=42,
        f_lasti=999,
        positions=[],
    )
    context = _context_for_frame(frame)
    assert context.lineno == 42
    assert context.colno == 1
    assert context.end_colno == 1


def test_context_for_frame_defaults_start_line_when_position_start_line_is_none():
    frame = _FakeFrame(
        filename="/tmp/fake.py",
        f_lineno=99,
        f_lasti=0,
        positions=[(None, None, None, None)],
    )
    context = _context_for_frame(frame)
    assert context.lineno == 99


def test_context_for_frame_defaults_end_line_when_position_end_line_is_none():
    frame = _FakeFrame(
        filename="/tmp/fake.py",
        f_lineno=7,
        f_lasti=0,
        positions=[(5, None, 2, 4)],
    )
    context = _context_for_frame(frame)
    assert context.lineno == 5
    assert context.colno == 3
    assert context.end_colno == 5


def test_context_for_frame_defaults_start_col_when_position_start_col_is_none():
    frame = _FakeFrame(
        filename="/tmp/fake.py",
        f_lineno=1,
        f_lasti=0,
        positions=[(1, 1, None, 5)],
    )
    context = _context_for_frame(frame)
    assert context.colno == 1
    assert context.end_colno == 6


def test_context_for_frame_defaults_end_col_when_position_end_col_is_none():
    frame = _FakeFrame(
        filename="/tmp/fake.py",
        f_lineno=1,
        f_lasti=0,
        positions=[(1, 1, 3, None)],
    )
    context = _context_for_frame(frame)
    assert context.colno == 4
    assert context.end_colno == 4


def test_caller_context_dataclass_is_frozen_and_holds_all_five_fields():
    context = CallerContext(filename="a.py", lineno=1, colno=1, end_colno=5, source_line="x = 1")
    assert context.filename == "a.py"
    assert context.source_line == "x = 1"