4
4
checks static typing of the code under test. (Running pytest checks dynamic behavior.)
5
5
"""
6
6
7
- import importlib
7
+ from itertools import groupby
8
8
from typing import Type
9
9
10
10
import pytest
11
+ from typing_extensions import assert_type
11
12
12
13
import git
13
14
14
15
15
- def test_cannot_get_undefined () -> None :
16
+ def test_cannot_access_undefined () -> None :
17
+ """Accessing a bogus attribute in git remains both a dynamic and static error."""
16
18
with pytest .raises (AttributeError ):
17
19
git .foo # type: ignore[attr-defined]
18
20
19
21
20
22
def test_cannot_import_undefined () -> None :
23
+ """Importing a bogus attribute from git remains both a dynamic and static error."""
21
24
with pytest .raises (ImportError ):
22
25
from git import foo # type: ignore[attr-defined] # noqa: F401
23
26
24
27
25
- def test_util_alias_members_resolve () -> None :
26
- """git.index.util members can be accessed via git.util, and mypy recognizes it."""
27
- gu_tfs = git .util .TemporaryFileSwap
28
- from git .index .util import TemporaryFileSwap
28
+ def test_util_alias_access () -> None :
29
+ """Accessing util in git works, warns, and mypy verifies it and its attributes."""
30
+ # The attribute access should succeed.
31
+ with pytest .deprecated_call () as ctx :
32
+ util = git .util
29
33
30
- def accepts_tfs_type (t : Type [TemporaryFileSwap ]) -> None :
31
- pass
34
+ # There should be exactly one warning and it should have our util-specific message.
35
+ (message ,) = [str (entry .message ) for entry in ctx ]
36
+ assert "git.util" in message
37
+ assert "git.index.util" in message
38
+ assert "should not be relied on" in message
32
39
33
- def rejects_tfs_type (t : Type [git .Git ]) -> None :
34
- pass
40
+ # We check access through the util alias to the TemporaryFileSwap member, since it
41
+ # is slightly simpler to validate and reason about than the other public members,
42
+ # which are functions (specifically, higher-order functions for use as decorators).
43
+ from git .index .util import TemporaryFileSwap
35
44
36
- # TODO: When typing_extensions is made a test dependency, use assert_type for this.
37
- accepts_tfs_type (gu_tfs )
38
- rejects_tfs_type (gu_tfs ) # type: ignore[arg-type]
45
+ assert_type (util .TemporaryFileSwap , Type [TemporaryFileSwap ])
39
46
40
- assert gu_tfs is TemporaryFileSwap
47
+ # This comes after the static assertion, just in case it would affect the inference.
48
+ assert util .TemporaryFileSwap is TemporaryFileSwap
41
49
42
50
43
- def test_util_alias_access_warns () -> None :
51
+ def test_util_alias_import () -> None :
52
+ """Importing util from git works, warns, and mypy verifies it and its attributes."""
53
+ # The import should succeed.
44
54
with pytest .deprecated_call () as ctx :
45
- git . util
55
+ from git import util
46
56
47
- assert len (ctx ) == 1
48
- message = str (ctx [0 ].message )
57
+ # There may be multiple warnings. In CPython there will be currently always be
58
+ # exactly two, possibly due to the equivalent of calling hasattr to do a pre-check
59
+ # prior to retrieving the attribute for actual use. However, all warnings should
60
+ # have the same message, and it should be our util-specific message.
61
+ (message ,) = {str (entry .message ) for entry in ctx }
49
62
assert "git.util" in message
50
63
assert "git.index.util" in message
51
64
assert "should not be relied on" in message
52
65
66
+ # As above, we check access through the util alias to the TemporaryFileSwap member.
67
+ from git .index .util import TemporaryFileSwap
53
68
54
- def test_util_alias_import_warns () -> None :
55
- with pytest .deprecated_call () as ctx :
56
- from git import util # noqa: F401
69
+ assert_type (util .TemporaryFileSwap , Type [TemporaryFileSwap ])
57
70
58
- message = str (ctx [0 ].message )
59
- assert "git.util" in message
60
- assert "git.index.util" in message
61
- assert "should not be relied on" in message
71
+ # This comes after the static assertion, just in case it would affect the inference.
72
+ assert util .TemporaryFileSwap is TemporaryFileSwap
62
73
63
74
64
75
# Split out util and have all its tests be separate, above.
@@ -71,12 +82,11 @@ def test_util_alias_import_warns() -> None:
71
82
git .index .base ,
72
83
git .index .fun ,
73
84
git .index .typ ,
74
- git .index .util ,
75
85
)
76
86
77
87
78
- def test_private_module_alias_access_on_git_module () -> None :
79
- """Private alias access works, warns, and except for util is a mypy error."""
88
+ def test_private_module_alias_access () -> None :
89
+ """Non-util private alias access works, warns, but is a deliberate mypy error."""
80
90
with pytest .deprecated_call () as ctx :
81
91
assert (
82
92
git .head , # type: ignore[attr-defined]
@@ -87,21 +97,16 @@ def test_private_module_alias_access_on_git_module() -> None:
87
97
git .base , # type: ignore[attr-defined]
88
98
git .fun , # type: ignore[attr-defined]
89
99
git .typ , # type: ignore[attr-defined]
90
- git .util ,
91
100
) == _MODULE_ALIAS_TARGETS
92
101
102
+ # Each should have warned exactly once, and note what to use instead.
93
103
messages = [str (w .message ) for w in ctx ]
94
- for target , message in zip (_MODULE_ALIAS_TARGETS [: - 1 ] , messages [: - 1 ] , strict = True ):
104
+ for target , message in zip (_MODULE_ALIAS_TARGETS , messages , strict = True ):
95
105
assert message .endswith (f"Use { target .__name__ } instead." )
96
106
97
- util_message = messages [- 1 ]
98
- assert "git.util" in util_message
99
- assert "git.index.util" in util_message
100
- assert "should not be relied on" in util_message
101
107
102
-
103
- def test_private_module_alias_import_from_git_module () -> None :
104
- """Private alias import works, warns, and except for util is a mypy error."""
108
+ def test_private_module_alias_import () -> None :
109
+ """Non-util private alias access works, warns, but is a deliberate mypy error."""
105
110
with pytest .deprecated_call () as ctx :
106
111
from git import head # type: ignore[attr-defined]
107
112
from git import log # type: ignore[attr-defined]
@@ -111,7 +116,6 @@ def test_private_module_alias_import_from_git_module() -> None:
111
116
from git import base # type: ignore[attr-defined]
112
117
from git import fun # type: ignore[attr-defined]
113
118
from git import typ # type: ignore[attr-defined]
114
- from git import util
115
119
116
120
assert (
117
121
head ,
@@ -122,17 +126,13 @@ def test_private_module_alias_import_from_git_module() -> None:
122
126
base ,
123
127
fun ,
124
128
typ ,
125
- util ,
126
129
) == _MODULE_ALIAS_TARGETS
127
130
128
- # FIXME: This fails because, with imports, multiple consecutive accesses may occur.
129
- # In practice, with CPython, it is always exactly two accesses, the first from the
130
- # equivalent of a hasattr, and the second to fetch the attribute intentionally.
131
- messages = [str (w .message ) for w in ctx ]
132
- for target , message in zip (_MODULE_ALIAS_TARGETS [:- 1 ], messages [:- 1 ], strict = True ):
131
+ # Each import may warn multiple times. In CPython there will be currently always be
132
+ # exactly two warnings per import, possibly due to the equivalent of calling hasattr
133
+ # to do a pre-check prior to retrieving the attribute for actual use. However, for
134
+ # each import, all messages should be the same and should note what to use instead.
135
+ messages_with_duplicates = [str (w .message ) for w in ctx ]
136
+ messages = [message for message , _ in groupby (messages_with_duplicates )]
137
+ for target , message in zip (_MODULE_ALIAS_TARGETS , messages , strict = True ):
133
138
assert message .endswith (f"Use { target .__name__ } instead." )
134
-
135
- util_message = messages [- 1 ]
136
- assert "git.util" in util_message
137
- assert "git.index.util" in util_message
138
- assert "should not be relied on" in util_message
0 commit comments