Skip to content

Commit e2fd852

Browse files
ilevkivskyiIvan Levkivskyi
and
Ivan Levkivskyi
authored
Add more consistency tests (#4983)
Follow up fo #4971 Co-authored-by: Ivan Levkivskyi <[email protected]>
1 parent 569fcea commit e2fd852

File tree

3 files changed

+127
-4
lines changed

3 files changed

+127
-4
lines changed

.github/workflows/tests.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@v2
1313
- uses: actions/setup-python@v2
14-
- run: ./tests/check_consistent.py
14+
- run: |
15+
pip install toml
16+
./tests/check_consistent.py
1517
1618
flake8:
1719
name: Lint with flake8
File renamed without changes.

tests/check_consistent.py

+124-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#!/usr/bin/env python3
22

3-
# For various reasons we need the contents of certain files to be
3+
# For security (and simplicity) reasons, only a limited kind of files can be
4+
# present in /stdlib and /stubs directories, see README for detail. Here we
5+
# verify these constraints.
6+
7+
# In addition, for various reasons we need the contents of certain files to be
48
# duplicated in two places, for example stdlib/@python2/builtins.pyi and
59
# stdlib/@python2/__builtin__.pyi must be identical. In the past we used
610
# symlinks but that doesn't always work on Windows, so now you must
@@ -10,13 +14,72 @@
1014
import filecmp
1115
import os
1216

17+
import toml
18+
1319
consistent_files = [
1420
{"stdlib/@python2/builtins.pyi", "stdlib/@python2/__builtin__.pyi"},
1521
{"stdlib/threading.pyi", "stdlib/_dummy_threading.pyi"},
1622
]
1723

1824

19-
def main():
25+
def assert_stubs_only(directory):
26+
"""Check that given directory contains only valid stub files."""
27+
top = directory.split(os.sep)[-1]
28+
assert top.isidentifier(), f"Bad directory name: {top}"
29+
for _, dirs, files in os.walk(directory):
30+
for file in files:
31+
name, ext = os.path.splitext(file)
32+
assert name.isidentifier(), f"Files must be valid modules, got: {name}"
33+
assert ext == ".pyi", f"Only stub flies allowed. Got: {file} in {directory}"
34+
for subdir in dirs:
35+
assert subdir.isidentifier(), f"Directories must be valid packages, got: {subdir}"
36+
37+
38+
def check_stdlib():
39+
for entry in os.listdir("stdlib"):
40+
if os.path.isfile(os.path.join("stdlib", entry)):
41+
name, ext = os.path.splitext(entry)
42+
if ext != ".pyi":
43+
assert entry == "VERSIONS", f"Unexpected file in stdlib root: {entry}"
44+
assert name.isidentifier(), "Bad file name in stdlib"
45+
else:
46+
if entry == "@python2":
47+
continue
48+
assert_stubs_only(os.path.join("stdlib", entry))
49+
for entry in os.listdir("stdlib/@python2"):
50+
if os.path.isfile(os.path.join("stdlib/@python2", entry)):
51+
name, ext = os.path.splitext(entry)
52+
assert name.isidentifier(), "Bad file name in stdlib"
53+
assert ext == ".pyi", "Unexpected file in stdlib/@python2 root"
54+
else:
55+
assert_stubs_only(os.path.join("stdlib/@python2", entry))
56+
57+
58+
def check_stubs():
59+
for distribution in os.listdir("stubs"):
60+
assert not os.path.isfile(distribution), f"Only directories allowed in stubs, got {distribution}"
61+
for entry in os.listdir(os.path.join("stubs", distribution)):
62+
if os.path.isfile(os.path.join("stubs", distribution, entry)):
63+
name, ext = os.path.splitext(entry)
64+
if ext != ".pyi":
65+
assert entry in {"METADATA.toml", "README", "README.md", "README.rst"}, entry
66+
else:
67+
assert name.isidentifier(), f"Bad file name '{entry}' in stubs"
68+
else:
69+
if entry == "@python2":
70+
continue
71+
assert_stubs_only(os.path.join("stubs", distribution, entry))
72+
if os.path.isdir(os.path.join("stubs", distribution, "@python2")):
73+
for entry in os.listdir(os.path.join("stubs", distribution, "@python2")):
74+
if os.path.isfile(os.path.join("stubs", distribution, "@python2", entry)):
75+
name, ext = os.path.splitext(entry)
76+
assert name.isidentifier(), f"Bad file name '{entry}' in stubs"
77+
assert ext == ".pyi", f"Unexpected file {entry} in @python2 stubs"
78+
else:
79+
assert_stubs_only(os.path.join("stubs", distribution, "@python2", entry))
80+
81+
82+
def check_same_files():
2083
files = [os.path.join(root, file) for root, dir, files in os.walk(".") for file in files]
2184
no_symlink = "You cannot use symlinks in typeshed, please copy {} to its link."
2285
for file in files:
@@ -34,5 +97,63 @@ def main():
3497
)
3598

3699

100+
def check_versions():
101+
versions = {}
102+
with open("stdlib/VERSIONS") as f:
103+
data = f.read().splitlines()
104+
for line in data:
105+
if not line or line.lstrip().startswith("#"):
106+
continue
107+
assert ": " in line, f"Bad line in VERSIONS: {line}"
108+
module, version = line.split(": ")
109+
msg = f"Unsupported Python version{version}"
110+
assert version.count(".") == 1, msg
111+
major, minor = version.split(".")
112+
assert major in {"2", "3"}, msg
113+
assert minor.isdigit(), msg
114+
assert module not in versions, f"Duplicate module {module} in VERSIONS"
115+
versions[module] = (int(major), int(minor))
116+
modules = set()
117+
for entry in os.listdir("stdlib"):
118+
if entry == "@python2" or entry == "VERSIONS":
119+
continue
120+
if os.path.isfile(os.path.join("stdlib", entry)):
121+
mod, _ = os.path.splitext(entry)
122+
modules.add(mod)
123+
else:
124+
modules.add(entry)
125+
extra = modules - set(versions)
126+
assert not extra, f"Modules not in versions: {extra}"
127+
extra = set(versions) - modules
128+
assert not extra, f"Versions not in modules: {extra}"
129+
130+
131+
def check_metadata():
132+
for distribution in os.listdir("stubs"):
133+
with open(os.path.join("stubs", distribution, "METADATA.toml")) as f:
134+
data = toml.loads(f.read())
135+
assert "version" in data, f"Missing version for {distribution}"
136+
version = data["version"]
137+
msg = f"Unsupported Python version {version}"
138+
assert version.count(".") == 1, msg
139+
major, minor = version.split(".")
140+
assert major.isdigit() and minor.isdigit(), msg
141+
for key in data:
142+
assert key in {
143+
"version", "python2", "python3", "requires"
144+
}, f"Unexpected key {key} for {distribution}"
145+
assert isinstance(data.get("python2", False), bool), f"Invalid python2 value for {distribution}"
146+
assert isinstance(data.get("python3", True), bool), f"Invalid python3 value for {distribution}"
147+
assert isinstance(data.get("requires", []), list), f"Invalid requires value for {distribution}"
148+
for dep in data.get("requires", []):
149+
# TODO: add more validation here.
150+
assert isinstance(dep, str), f"Invalid dependency {dep} for {distribution}"
151+
assert dep.startswith("types-"), f"Only stub dependencies supported, got {dep}"
152+
153+
37154
if __name__ == "__main__":
38-
main()
155+
check_stdlib()
156+
check_versions()
157+
check_stubs()
158+
check_metadata()
159+
check_same_files()

0 commit comments

Comments
 (0)