8000 Make missing `long_description` check more flexible (#887) · pypa/twine@4931a2a · GitHub
[go: up one dir, main page]

Skip to content

Commit 4931a2a

Browse files
authored
Make missing long_description check more flexible (#887)
* Make missing long_description check more flexible. A missing `long_description` results in `UNKNOWN` being written to PKG-INFO, but at some point, the number of trailing newlines changed. See pypa/readme_renderer#231 (comment) * Rewrite tests using `build` * Attempt to detect missing description on Windows * Reconfigure output before each test * Add changelong entry
1 parent 7cd0b23 commit 4931a2a

File tree

6 files changed

+228
-115
lines changed

6 files changed

+228
-115
lines changed

changelog/887.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Restore warning for missing ``long_description``.

tests/conftest.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
11
import getpass
2+
import logging.config
23
import textwrap
34

45
import pytest
6+
import rich
57

68
from twine import settings
79
from twine import utils
810

911

12+
@pytest.fixture(autouse=True)
13+
def configure_output():
14+
"""
15+
Disable colored output and line wrapping before each test.
16+
17+
Some tests (e.g. test_main.py) will end up calling (and making assertions based on)
18+
twine.cli.configure_output, which overrides this configuration. This fixture should
19+
prevent that leaking into subsequent tests.
20+
"""
21+
rich.reconfigure(
22+
no_color=True,
23+
width=500,
24+
)
25+
26+
logging.config.dictConfig(
27+
{
28+
"version": 1,
29+
"handlers": {
30+
"console": {
31+
"class": "logging.StreamHandler",
32+
}
33+
},
34+
"loggers": {
35+
"twine": {
36+
"handlers": ["console"],
37+
},
38+
},
39+
}
40+
)
41+
42+
1043
@pytest.fixture()
1144
def config_file(tmpdir, monkeypatch):
1245
path = tmpdir / ".pypirc"

tests/test_check.py

Lines changed: 180 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import logging
15+
import textwrap
1516

17+
import build
1618
import pretend
1719
import pytest
1820

19-
from twine import commands
20-
from twine import package as package_file
2121
from twine.commands import check
2222

2323

@@ -38,10 +38,8 @@ def test_str_representation(self):
3838
assert str(self.stream) == "line 2: Warning: Title underline too short."
3939

4040

41-
def test_check_no_distributions(monkeypatch, caplog):
42-
monkeypatch.setattr(commands, "_find_dists", lambda a: [])
43-
44-
assert not check.check(["dist/*"])
41+
def test_fails_no_distributions(caplog):
42+
assert not check.check([])
4543
assert caplog.record_tuples == [
4644
(
4745
"twine.commands.check",
@@ -51,75 +49,51 @@ def test_check_no_distributions(monkeypatch, caplog):
5149
]
5250

5351

54-
def test_check_passing_distribution(monkeypatch, capsys):
55-
renderer = pretend.stub(render=pretend.call_recorder(lambda *a, **kw: "valid"))
56-
package = pretend.stub(
57-
metadata_dictionary=lambda: {
58-
"description": "blah",
59-
"description_content_type": "text/markdown",
60-
}
61-
)
62-
warning_stream = ""
63-
64-
monkeypatch.setattr(check, "_RENDERERS", {None: renderer})
65-
monkeypatch.setattr(commands, "_find_dists", lambda a: ["dist/dist.tar.gz"])
66-
monkeypatch.setattr(
67-
package_file,
68-
"PackageFile",
69-
pretend.stub(from_filename=lambda *a, **kw: package),
70-
)
71-
monkeypatch.setattr(check, "_WarningStream", lambda: warning_stream)
72-
73-
assert not check.check(["dist/*"])
74-
assert capsys.readouterr().out == "Checking dist/dist.tar.gz: PASSED\n"
75-
assert renderer.render.calls == [pretend.call("blah", stream=warning_stream)]
76-
77-
78-
@pytest.mark.parametrize("content_type", ["text/plain", "text/markdown"])
79-
def test_check_passing_distribution_with_none_renderer(
80-
content_type,
81-
monkeypatch,
82-
capsys,
83-
):
84-
"""Pass when rendering a content type can't fail."""
85-
package = pretend.stub(
86-
metadata_dictionary=lambda: {
87-
"description": "blah",
88-
"description_content_type": content_type,
89-
}
90-
)
52+
def build_sdist(src_path, project_files):
53+
"""
54+
Build a source distribution similar to `python3 -m build --sdist`.
9155
92-
monkeypatch.setattr(commands, "_find_dists", lambda a: ["dist/dist.tar.gz"])
93-
monkeypatch.setattr(
94-
package_file,
95-
"PackageFile",
96-
pretend.stub(from_filename=lambda *a, **kw: package),
56+
Returns the absolute path of the built distribution.
57+
"""
58+
project_files = {
59+
"pyproject.toml": (
60+
"""
61+
[build-system]
62+
requires = ["setuptools"]
63+
build-backend = "setuptools.build_meta"
64+
"""
65+
),
66+
**project_files,
67+
}
68+
69+
for filename, content in project_files.items():
70+
(src_path / filename).write_text(textwrap.dedent(content))
71+
72+
builder = build.ProjectBuilder(src_path)
73+
return builder.build("sdist", str(src_path / "dist"))
74+
75+
76+
@pytest.mark.parametrize("strict", [False, True])
77+
def test_warns_missing_description(strict, tmp_path, capsys, caplog):
78+
sdist = build_sdist(
79+
tmp_path,
80+
{
81+
"setup.cfg": (
82+
"""
83+
[metadata]
84+
name = test-package
85+
version = 0.0.1
86+
"""
87+
),
88+
},
9789
)
9890

99-
assert not check.check(["dist/*"])
100-
assert capsys.readouterr().out == "Checking dist/dist.tar.gz: PASSED\n"
91+
assert check.check([sdist], strict=strict) is strict
10192

102-
103-
def test_check_no_description(monkeypatch, capsys, caplog):
104-
package = pretend.stub(
105-
metadata_dictionary=lambda: {
106-
"description": None,
107-
"description_content_type": None,
108-
}
109-
)
110-
111-
monkeypatch.setattr(commands, "_find_dists", lambda a: ["dist/dist.tar.gz"])
112-
monkeypatch.setattr(
113-
package_file,
114-
"PackageFile",
115-
pretend.stub(from_filename=lambda *a, **kw: package),
93+
assert capsys.readouterr().out == f"Checking {sdist}: " + (
94+
"FAILED due to warnings\n" if strict else "PASSED with warnings\n"
11695
)
11796

118-
assert not check.check(["dist/*"])
119-
120-
assert capsys.readouterr().out == (
121-
"Checking dist/dist.tar.gz: PASSED with warnings\n"
122-
)
12397
assert caplog.record_tuples == [
12498
(
12599
"twine.commands.check",
@@ -134,71 +108,167 @@ def test_check_no_description(monkeypatch, capsys, caplog):
134108
]
135109

136110

137-
def test_strict_fails_on_warnings(monkeypatch, capsys, caplog):
138-
package = pretend.stub(
139-
metadata_dictionary=lambda: {
140-
"description": None,
141-
"description_content_type": None,
142-
}
111+
def test_warns_missing_file(tmp_path, capsys, caplog):
112+
sdist = build_sdist(
113+
tmp_path,
114+
{
115+
"setup.cfg": (
116+
"""
117+
[metadata]
118+
name = test-package
119+
version = 0.0.1
120+
long_description = file:README.rst
121+
long_description_content_type = text/x-rst
122+
"""
123+
),
124+
},
143125
)
144126

145-
monkeypatch.setattr(commands, "_find_dists", lambda a: ["dist/dist.tar.gz"])
146-
monkeypatch.setattr(
147-
package_file,
148-
"PackageFile",
149-
pretend.stub(from_filename=lambda *a, **kw: package),
150-
)
127+
assert not check.check([sdist])
151128

152-
assert check.check(["dist/*"], strict=True)
129+
assert capsys.readouterr().out == f"Checking {sdist}: PASSED with warnings\n"
153130

154-
assert capsys.readouterr().out == (
155-
"Checking dist/dist.tar.gz: FAILED due to warnings\n"
156-
)
157131
assert caplog.record_tuples == [
158132
(
159133
"twine.commands.check",
160134
logging.WARNING,
161-
"`long_description_content_type` missing. defaulting to `text/x-rst`.",
135+
"`long_description` missing.",
162136
),
137+
]
138+
139+
140+
def test_fails_rst_syntax_error(tmp_path, capsys, caplog):
141+
sdist = build_sdist(
142+
tmp_path,
143+
{
144+
"setup.cfg": (
145+
"""
146+
[metadata]
147+
name = test-package
148+
version = 0.0.1
149+
long_description = file:README.rst
150+
long_description_content_type = text/x-rst
151+
"""
152+
),
153+
"README.rst": (
154+
"""
155+
============
156+
"""
157+
),
158+
},
159+
)
160+
161+
assert check.check([sdist])
162+
163+
assert capsys.readouterr().out == f"Checking {sdist}: FAILED\n"
164+
165+
assert caplog.record_tuples == [
163166
(
164167
"twine.commands.check",
165-
logging.WARNING,
166-
"`long_description` missing.",
168+
logging.ERROR,
169+
"`long_description` has syntax errors in markup "
170+
"and would not be rendered on PyPI.\n"
171+
"line 2: Error: Document or section may not begin with a transition.",
167172
),
168173
]
169174

170175

171-
def test_check_failing_distribution(monkeypatch, capsys, caplog):
172-
renderer = pretend.stub(render=pretend.call_recorder(lambda *a, **kw: None))
173-
package = pretend.stub(
174-
metadata_dictionary=lambda: {
175-
"description": "blah",
176-
"description_content_type": "text/markdown",
177-
}
176+
def test_fails_rst_no_content(tmp_path, capsys, caplog):
177+
sdist = build_sdist(
178+
tmp_path,
179+
{
180+
"setup.cfg": (
181+
"""
182+
[metadata]
183+
name = test-package
184+
version = 0.0.1
185+
long_description = file:README.rst
186+
long_description_content_type = text/x-rst
187+
"""
188+
),
189+
"README.rst": (
190+
"""
191+
test-package
192+
============
193+
"""
194+
),
195+
},
178196
)
179-
warning_stream = "Syntax error"
180-
181-
monkeypatch.setattr(check, "_RENDERERS", {None: renderer})
182-
monkeypatch.setattr(commands, "_find_dists", lambda a: ["dist/dist.tar.gz"])
183-
monkeypatch.setattr(
184-
package_file,
185-
"PackageFile",
186-
pretend.stub(from_filename=lambda *a, **kw: package),
187-
)
188-
monkeypatch.setattr(check, "_WarningStream", lambda: warning_stream)
189197

190-
assert check.check(["dist/*"])
198+
assert check.check([sdist])
199+
200+
assert capsys.readouterr().out == f"Checking {sdist}: FAILED\n"
191201

192-
assert capsys.readouterr().out == "Checking dist/dist.tar.gz: FAILED\n"
193202
assert caplog.record_tuples == [
194203
(
195204
"twine.commands.check",
196205
logging.ERROR,
197-
"`long_description` has syntax errors in markup and would not be rendered "
198-
"on PyPI.\nSyntax error",
206+
"`long_description` has syntax errors in markup "
207+
"and would not be rendered on PyPI.\n",
199208
),
200209
]
201-
assert renderer.render.calls == [pretend.call("blah", stream=warning_stream)]
210+
211+
212+
def test_passes_rst_description(tmp_path, capsys, caplog):
213+
sdist = build_sdist(
214+
tmp_path,
215+
{
216+
"setup.cfg": (
217+
"""
218+
[metadata]
219+
name = test-package
220+
version = 0.0.1
221+
long_description = file:README.rst
222+
long_description_content_type = text/x-rst
223+
"""
224+
),
225+
"README.rst": (
226+
"""
227+
test-package
228+
============
229+
230+
A test package.
231+
"""
232+
),
233+
},
234+
)
235+
236+
assert not check.check([sdist])
237+
238+
assert capsys.readouterr().out == f"Checking {sdist}: PASSED\n"
239+
240+
assert not caplog.record_tuples
241+
242+
243+
@pytest.mark.parametrize("content_type", ["text/markdown", "text/plain"])
244+
def test_passes_markdown_description(content_type, tmp_path, capsys, caplog):
245+
sdist = build_sdist(
246+
tmp_path,
247+
{
248+
"setup.cfg": (
249+
f"""
250+
[metadata]
251+
name = test-package
252+
version = 0.0.1
253+
long_description = file:README.md
254+
long_description_content_type = {content_type}
255+
"""
256+
),
257+
"README.md": (
258+
"""
259+
# test-package
260+
261+
A test package.
262+
"""
263+
),
264+
},
265+
)
266+
267+
assert not check.check([sdist])
268+
269+
assert capsys.readouterr().out == f"Checking {sdist}: PASSED\n"
270+
271+
assert not caplog.record_tuples
202272

203273

204274
def test_main(monkeypatch):

0 commit comments

Comments
 (0)
0