1import os
2import sys
3import shutil
4import signal
5import tarfile
6import importlib
7import contextlib
8from concurrent import futures
9import re
10from zipfile import ZipFile
11
12import pytest
13from jaraco import path
14
15from .textwrap import DALS
16
17SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
18
19
20TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180"))  # in seconds
21IS_PYPY = '__pypy__' in sys.builtin_module_names
22
23
24pytestmark = pytest.mark.skipif(
25    sys.platform == "win32" and IS_PYPY,
26    reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor "
27    "is flaky and problematic"
28)
29
30
31class BuildBackendBase:
32    def __init__(self, cwd='.', env={}, backend_name='setuptools.build_meta'):
33        self.cwd = cwd
34        self.env = env
35        self.backend_name = backend_name
36
37
38class BuildBackend(BuildBackendBase):
39    """PEP 517 Build Backend"""
40
41    def __init__(self, *args, **kwargs):
42        super(BuildBackend, self).__init__(*args, **kwargs)
43        self.pool = futures.ProcessPoolExecutor(max_workers=1)
44
45    def __getattr__(self, name):
46        """Handles aribrary function invocations on the build backend."""
47
48        def method(*args, **kw):
49            root = os.path.abspath(self.cwd)
50            caller = BuildBackendCaller(root, self.env, self.backend_name)
51            pid = None
52            try:
53                pid = self.pool.submit(os.getpid).result(TIMEOUT)
54                return self.pool.submit(caller, name, *args, **kw).result(TIMEOUT)
55            except futures.TimeoutError:
56                self.pool.shutdown(wait=False)  # doesn't stop already running processes
57                self._kill(pid)
58                pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)")
59            except (futures.process.BrokenProcessPool, MemoryError, OSError):
60                if IS_PYPY:
61                    pytest.xfail("PyPy frequently fails tests with ProcessPoolExector")
62                raise
63
64        return method
65
66    def _kill(self, pid):
67        if pid is None:
68            return
69        with contextlib.suppress(ProcessLookupError, OSError):
70            os.kill(pid, signal.SIGTERM if os.name == "nt" else signal.SIGKILL)
71
72
73class BuildBackendCaller(BuildBackendBase):
74    def __init__(self, *args, **kwargs):
75        super(BuildBackendCaller, self).__init__(*args, **kwargs)
76
77        (self.backend_name, _,
78         self.backend_obj) = self.backend_name.partition(':')
79
80    def __call__(self, name, *args, **kw):
81        """Handles aribrary function invocations on the build backend."""
82        os.chdir(self.cwd)
83        os.environ.update(self.env)
84        mod = importlib.import_module(self.backend_name)
85
86        if self.backend_obj:
87            backend = getattr(mod, self.backend_obj)
88        else:
89            backend = mod
90
91        return getattr(backend, name)(*args, **kw)
92
93
94defns = [
95    {  # simple setup.py script
96        'setup.py': DALS("""
97            __import__('setuptools').setup(
98                name='foo',
99                version='0.0.0',
100                py_modules=['hello'],
101                setup_requires=['six'],
102            )
103            """),
104        'hello.py': DALS("""
105            def run():
106                print('hello')
107            """),
108    },
109    {  # setup.py that relies on __name__
110        'setup.py': DALS("""
111            assert __name__ == '__main__'
112            __import__('setuptools').setup(
113                name='foo',
114                version='0.0.0',
115                py_modules=['hello'],
116                setup_requires=['six'],
117            )
118            """),
119        'hello.py': DALS("""
120            def run():
121                print('hello')
122            """),
123    },
124    {  # setup.py script that runs arbitrary code
125        'setup.py': DALS("""
126            variable = True
127            def function():
128                return variable
129            assert variable
130            __import__('setuptools').setup(
131                name='foo',
132                version='0.0.0',
133                py_modules=['hello'],
134                setup_requires=['six'],
135            )
136            """),
137        'hello.py': DALS("""
138            def run():
139                print('hello')
140            """),
141    },
142    {  # setup.py script that constructs temp files to be included in the distribution
143        'setup.py': DALS("""
144            # Some packages construct files on the fly, include them in the package,
145            # and immediately remove them after `setup()` (e.g. pybind11==2.9.1).
146            # Therefore, we cannot use `distutils.core.run_setup(..., stop_after=...)`
147            # to obtain a distribution object first, and then run the distutils
148            # commands later, because these files will be removed in the meantime.
149
150            with open('world.py', 'w') as f:
151                f.write('x = 42')
152
153            try:
154                __import__('setuptools').setup(
155                    name='foo',
156                    version='0.0.0',
157                    py_modules=['world'],
158                    setup_requires=['six'],
159                )
160            finally:
161                # Some packages will clean temporary files
162                __import__('os').unlink('world.py')
163            """),
164    },
165    {  # setup.cfg only
166        'setup.cfg': DALS("""
167        [metadata]
168        name = foo
169        version = 0.0.0
170
171        [options]
172        py_modules=hello
173        setup_requires=six
174        """),
175        'hello.py': DALS("""
176        def run():
177            print('hello')
178        """)
179    },
180    {  # setup.cfg and setup.py
181        'setup.cfg': DALS("""
182        [metadata]
183        name = foo
184        version = 0.0.0
185
186        [options]
187        py_modules=hello
188        setup_requires=six
189        """),
190        'setup.py': "__import__('setuptools').setup()",
191        'hello.py': DALS("""
192        def run():
193            print('hello')
194        """)
195    },
196]
197
198
199class TestBuildMetaBackend:
200    backend_name = 'setuptools.build_meta'
201
202    def get_build_backend(self):
203        return BuildBackend(backend_name=self.backend_name)
204
205    @pytest.fixture(params=defns)
206    def build_backend(self, tmpdir, request):
207        path.build(request.param, prefix=str(tmpdir))
208        with tmpdir.as_cwd():
209            yield self.get_build_backend()
210
211    def test_get_requires_for_build_wheel(self, build_backend):
212        actual = build_backend.get_requires_for_build_wheel()
213        expected = ['six', 'wheel']
214        assert sorted(actual) == sorted(expected)
215
216    def test_get_requires_for_build_sdist(self, build_backend):
217        actual = build_backend.get_requires_for_build_sdist()
218        expected = ['six']
219        assert sorted(actual) == sorted(expected)
220
221    def test_build_wheel(self, build_backend):
222        dist_dir = os.path.abspath('pip-wheel')
223        os.makedirs(dist_dir)
224        wheel_name = build_backend.build_wheel(dist_dir)
225
226        wheel_file = os.path.join(dist_dir, wheel_name)
227        assert os.path.isfile(wheel_file)
228
229        # Temporary files should be removed
230        assert not os.path.isfile('world.py')
231
232        with ZipFile(wheel_file) as zipfile:
233            wheel_contents = set(zipfile.namelist())
234
235        # Each one of the examples have a single module
236        # that should be included in the distribution
237        python_scripts = (f for f in wheel_contents if f.endswith('.py'))
238        modules = [f for f in python_scripts if not f.endswith('setup.py')]
239        assert len(modules) == 1
240
241    @pytest.mark.parametrize('build_type', ('wheel', 'sdist'))
242    def test_build_with_existing_file_present(self, build_type, tmpdir_cwd):
243        # Building a sdist/wheel should still succeed if there's
244        # already a sdist/wheel in the destination directory.
245        files = {
246            'setup.py': "from setuptools import setup\nsetup()",
247            'VERSION': "0.0.1",
248            'setup.cfg': DALS("""
249                [metadata]
250                name = foo
251                version = file: VERSION
252            """),
253            'pyproject.toml': DALS("""
254                [build-system]
255                requires = ["setuptools", "wheel"]
256                build-backend = "setuptools.build_meta"
257            """),
258        }
259
260        path.build(files)
261
262        dist_dir = os.path.abspath('preexisting-' + build_type)
263
264        build_backend = self.get_build_backend()
265        build_method = getattr(build_backend, 'build_' + build_type)
266
267        # Build a first sdist/wheel.
268        # Note: this also check the destination directory is
269        # successfully created if it does not exist already.
270        first_result = build_method(dist_dir)
271
272        # Change version.
273        with open("VERSION", "wt") as version_file:
274            version_file.write("0.0.2")
275
276        # Build a *second* sdist/wheel.
277        second_result = build_method(dist_dir)
278
279        assert os.path.isfile(os.path.join(dist_dir, first_result))
280        assert first_result != second_result
281
282        # And if rebuilding the exact same sdist/wheel?
283        open(os.path.join(dist_dir, second_result), 'w').close()
284        third_result = build_method(dist_dir)
285        assert third_result == second_result
286        assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0
287
288    @pytest.mark.parametrize("setup_script", [None, SETUP_SCRIPT_STUB])
289    def test_build_with_pyproject_config(self, tmpdir, setup_script):
290        files = {
291            'pyproject.toml': DALS("""
292                [build-system]
293                requires = ["setuptools", "wheel"]
294                build-backend = "setuptools.build_meta"
295
296                [project]
297                name = "foo"
298                license = {text = "MIT"}
299                description = "This is a Python package"
300                dynamic = ["version", "readme"]
301                classifiers = [
302                    "Development Status :: 5 - Production/Stable",
303                    "Intended Audience :: Developers"
304                ]
305                urls = {Homepage = "http://github.com"}
306                dependencies = [
307                    "appdirs",
308                ]
309
310                [project.optional-dependencies]
311                all = [
312                    "tomli>=1",
313                    "pyscaffold>=4,<5",
314                    'importlib; python_version == "2.6"',
315                ]
316
317                [project.scripts]
318                foo = "foo.cli:main"
319
320                [tool.setuptools]
321                zip-safe = false
322                package-dir = {"" = "src"}
323                packages = {find = {where = ["src"]}}
324                license-files = ["LICENSE*"]
325
326                [tool.setuptools.dynamic]
327                version = {attr = "foo.__version__"}
328                readme = {file = "README.rst"}
329
330                [tool.distutils.sdist]
331                formats = "gztar"
332
333                [tool.distutils.bdist_wheel]
334                universal = true
335                """),
336            "MANIFEST.in": DALS("""
337                global-include *.py *.txt
338                global-exclude *.py[cod]
339                """),
340            "README.rst": "This is a ``README``",
341            "LICENSE.txt": "---- placeholder MIT license ----",
342            "src": {
343                "foo": {
344                    "__init__.py": "__version__ = '0.1'",
345                    "cli.py": "def main(): print('hello world')",
346                    "data.txt": "def main(): print('hello world')",
347                }
348            }
349        }
350        if setup_script:
351            files["setup.py"] = setup_script
352
353        build_backend = self.get_build_backend()
354        with tmpdir.as_cwd():
355            path.build(files)
356            sdist_path = build_backend.build_sdist("temp")
357            wheel_file = build_backend.build_wheel("temp")
358
359        with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
360            sdist_contents = set(tar.getnames())
361
362        with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
363            wheel_contents = set(zipfile.namelist())
364            metadata = str(zipfile.read("foo-0.1.dist-info/METADATA"), "utf-8")
365            license = str(zipfile.read("foo-0.1.dist-info/LICENSE.txt"), "utf-8")
366            epoints = str(zipfile.read("foo-0.1.dist-info/entry_points.txt"), "utf-8")
367
368        assert sdist_contents - {"foo-0.1/setup.py"} == {
369            'foo-0.1',
370            'foo-0.1/LICENSE.txt',
371            'foo-0.1/MANIFEST.in',
372            'foo-0.1/PKG-INFO',
373            'foo-0.1/README.rst',
374            'foo-0.1/pyproject.toml',
375            'foo-0.1/setup.cfg',
376            'foo-0.1/src',
377            'foo-0.1/src/foo',
378            'foo-0.1/src/foo/__init__.py',
379            'foo-0.1/src/foo/cli.py',
380            'foo-0.1/src/foo/data.txt',
381            'foo-0.1/src/foo.egg-info',
382            'foo-0.1/src/foo.egg-info/PKG-INFO',
383            'foo-0.1/src/foo.egg-info/SOURCES.txt',
384            'foo-0.1/src/foo.egg-info/dependency_links.txt',
385            'foo-0.1/src/foo.egg-info/entry_points.txt',
386            'foo-0.1/src/foo.egg-info/requires.txt',
387            'foo-0.1/src/foo.egg-info/top_level.txt',
388            'foo-0.1/src/foo.egg-info/not-zip-safe',
389        }
390        assert wheel_contents == {
391            "foo/__init__.py",
392            "foo/cli.py",
393            "foo/data.txt",  # include_package_data defaults to True
394            "foo-0.1.dist-info/LICENSE.txt",
395            "foo-0.1.dist-info/METADATA",
396            "foo-0.1.dist-info/WHEEL",
397            "foo-0.1.dist-info/entry_points.txt",
398            "foo-0.1.dist-info/top_level.txt",
399            "foo-0.1.dist-info/RECORD",
400        }
401        assert license == "---- placeholder MIT license ----"
402        for line in (
403            "Summary: This is a Python package",
404            "License: MIT",
405            "Classifier: Intended Audience :: Developers",
406            "Requires-Dist: appdirs",
407            "Requires-Dist: tomli (>=1) ; extra == 'all'",
408            "Requires-Dist: importlib ; (python_version == \"2.6\") and extra == 'all'"
409        ):
410            assert line in metadata
411
412        assert metadata.strip().endswith("This is a ``README``")
413        assert epoints.strip() == "[console_scripts]\nfoo = foo.cli:main"
414
415    def test_static_metadata_in_pyproject_config(self, tmpdir):
416        # Make sure static metadata in pyproject.toml is not overwritten by setup.py
417        # as required by PEP 621
418        files = {
419            'pyproject.toml': DALS("""
420                [build-system]
421                requires = ["setuptools", "wheel"]
422                build-backend = "setuptools.build_meta"
423
424                [project]
425                name = "foo"
426                description = "This is a Python package"
427                version = "42"
428                dependencies = ["six"]
429                """),
430            'hello.py': DALS("""
431                def run():
432                    print('hello')
433                """),
434            'setup.py': DALS("""
435                __import__('setuptools').setup(
436                    name='bar',
437                    version='13',
438                )
439                """),
440        }
441        build_backend = self.get_build_backend()
442        with tmpdir.as_cwd():
443            path.build(files)
444            sdist_path = build_backend.build_sdist("temp")
445            wheel_file = build_backend.build_wheel("temp")
446
447        assert (tmpdir / "temp/foo-42.tar.gz").exists()
448        assert (tmpdir / "temp/foo-42-py3-none-any.whl").exists()
449        assert not (tmpdir / "temp/bar-13.tar.gz").exists()
450        assert not (tmpdir / "temp/bar-42.tar.gz").exists()
451        assert not (tmpdir / "temp/foo-13.tar.gz").exists()
452        assert not (tmpdir / "temp/bar-13-py3-none-any.whl").exists()
453        assert not (tmpdir / "temp/bar-42-py3-none-any.whl").exists()
454        assert not (tmpdir / "temp/foo-13-py3-none-any.whl").exists()
455
456        with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
457            pkg_info = str(tar.extractfile('foo-42/PKG-INFO').read(), "utf-8")
458            members = tar.getnames()
459            assert "bar-13/PKG-INFO" not in members
460
461        with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
462            metadata = str(zipfile.read("foo-42.dist-info/METADATA"), "utf-8")
463            members = zipfile.namelist()
464            assert "bar-13.dist-info/METADATA" not in members
465
466        for file in pkg_info, metadata:
467            for line in ("Name: foo", "Version: 42"):
468                assert line in file
469            for line in ("Name: bar", "Version: 13"):
470                assert line not in file
471
472    def test_build_sdist(self, build_backend):
473        dist_dir = os.path.abspath('pip-sdist')
474        os.makedirs(dist_dir)
475        sdist_name = build_backend.build_sdist(dist_dir)
476
477        assert os.path.isfile(os.path.join(dist_dir, sdist_name))
478
479    def test_prepare_metadata_for_build_wheel(self, build_backend):
480        dist_dir = os.path.abspath('pip-dist-info')
481        os.makedirs(dist_dir)
482
483        dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
484
485        assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
486
487    def test_build_sdist_explicit_dist(self, build_backend):
488        # explicitly specifying the dist folder should work
489        # the folder sdist_directory and the ``--dist-dir`` can be the same
490        dist_dir = os.path.abspath('dist')
491        sdist_name = build_backend.build_sdist(dist_dir)
492        assert os.path.isfile(os.path.join(dist_dir, sdist_name))
493
494    def test_build_sdist_version_change(self, build_backend):
495        sdist_into_directory = os.path.abspath("out_sdist")
496        os.makedirs(sdist_into_directory)
497
498        sdist_name = build_backend.build_sdist(sdist_into_directory)
499        assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
500
501        # if the setup.py changes subsequent call of the build meta
502        # should still succeed, given the
503        # sdist_directory the frontend specifies is empty
504        setup_loc = os.path.abspath("setup.py")
505        if not os.path.exists(setup_loc):
506            setup_loc = os.path.abspath("setup.cfg")
507
508        with open(setup_loc, 'rt') as file_handler:
509            content = file_handler.read()
510        with open(setup_loc, 'wt') as file_handler:
511            file_handler.write(
512                content.replace("version='0.0.0'", "version='0.0.1'"))
513
514        shutil.rmtree(sdist_into_directory)
515        os.makedirs(sdist_into_directory)
516
517        sdist_name = build_backend.build_sdist("out_sdist")
518        assert os.path.isfile(
519            os.path.join(os.path.abspath("out_sdist"), sdist_name))
520
521    def test_build_sdist_pyproject_toml_exists(self, tmpdir_cwd):
522        files = {
523            'setup.py': DALS("""
524                __import__('setuptools').setup(
525                    name='foo',
526                    version='0.0.0',
527                    py_modules=['hello']
528                )"""),
529            'hello.py': '',
530            'pyproject.toml': DALS("""
531                [build-system]
532                requires = ["setuptools", "wheel"]
533                build-backend = "setuptools.build_meta"
534                """),
535        }
536        path.build(files)
537        build_backend = self.get_build_backend()
538        targz_path = build_backend.build_sdist("temp")
539        with tarfile.open(os.path.join("temp", targz_path)) as tar:
540            assert any('pyproject.toml' in name for name in tar.getnames())
541
542    def test_build_sdist_setup_py_exists(self, tmpdir_cwd):
543        # If build_sdist is called from a script other than setup.py,
544        # ensure setup.py is included
545        path.build(defns[0])
546
547        build_backend = self.get_build_backend()
548        targz_path = build_backend.build_sdist("temp")
549        with tarfile.open(os.path.join("temp", targz_path)) as tar:
550            assert any('setup.py' in name for name in tar.getnames())
551
552    def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
553        # Ensure that MANIFEST.in can exclude setup.py
554        files = {
555            'setup.py': DALS("""
556        __import__('setuptools').setup(
557            name='foo',
558            version='0.0.0',
559            py_modules=['hello']
560        )"""),
561            'hello.py': '',
562            'MANIFEST.in': DALS("""
563        exclude setup.py
564        """)
565        }
566
567        path.build(files)
568
569        build_backend = self.get_build_backend()
570        targz_path = build_backend.build_sdist("temp")
571        with tarfile.open(os.path.join("temp", targz_path)) as tar:
572            assert not any('setup.py' in name for name in tar.getnames())
573
574    def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
575        files = {
576            'setup.py': DALS("""
577                __import__('setuptools').setup(
578                    name='foo',
579                    version='0.0.0',
580                    py_modules=['hello']
581                )"""),
582            'hello.py': '',
583            'setup.cfg': DALS("""
584                [sdist]
585                formats=zip
586                """)
587        }
588
589        path.build(files)
590
591        build_backend = self.get_build_backend()
592        build_backend.build_sdist("temp")
593
594    _relative_path_import_files = {
595        'setup.py': DALS("""
596            __import__('setuptools').setup(
597                name='foo',
598                version=__import__('hello').__version__,
599                py_modules=['hello']
600            )"""),
601        'hello.py': '__version__ = "0.0.0"',
602        'setup.cfg': DALS("""
603            [sdist]
604            formats=zip
605            """)
606    }
607
608    def test_build_sdist_relative_path_import(self, tmpdir_cwd):
609        path.build(self._relative_path_import_files)
610        build_backend = self.get_build_backend()
611        with pytest.raises(ImportError, match="^No module named 'hello'$"):
612            build_backend.build_sdist("temp")
613
614    @pytest.mark.parametrize('setup_literal, requirements', [
615        ("'foo'", ['foo']),
616        ("['foo']", ['foo']),
617        (r"'foo\n'", ['foo']),
618        (r"'foo\n\n'", ['foo']),
619        ("['foo', 'bar']", ['foo', 'bar']),
620        (r"'# Has a comment line\nfoo'", ['foo']),
621        (r"'foo # Has an inline comment'", ['foo']),
622        (r"'foo \\\n >=3.0'", ['foo>=3.0']),
623        (r"'foo\nbar'", ['foo', 'bar']),
624        (r"'foo\nbar\n'", ['foo', 'bar']),
625        (r"['foo\n', 'bar\n']", ['foo', 'bar']),
626    ])
627    @pytest.mark.parametrize('use_wheel', [True, False])
628    def test_setup_requires(self, setup_literal, requirements, use_wheel,
629                            tmpdir_cwd):
630
631        files = {
632            'setup.py': DALS("""
633                from setuptools import setup
634
635                setup(
636                    name="qux",
637                    version="0.0.0",
638                    py_modules=["hello"],
639                    setup_requires={setup_literal},
640                )
641            """).format(setup_literal=setup_literal),
642            'hello.py': DALS("""
643            def run():
644                print('hello')
645            """),
646        }
647
648        path.build(files)
649
650        build_backend = self.get_build_backend()
651
652        if use_wheel:
653            base_requirements = ['wheel']
654            get_requires = build_backend.get_requires_for_build_wheel
655        else:
656            base_requirements = []
657            get_requires = build_backend.get_requires_for_build_sdist
658
659        # Ensure that the build requirements are properly parsed
660        expected = sorted(base_requirements + requirements)
661        actual = get_requires()
662
663        assert expected == sorted(actual)
664
665    def test_setup_requires_with_auto_discovery(self, tmpdir_cwd):
666        # Make sure patches introduced to retrieve setup_requires don't accidentally
667        # activate auto-discovery and cause problems due to the incomplete set of
668        # attributes passed to MinimalDistribution
669        files = {
670            'pyproject.toml': DALS("""
671                [project]
672                name = "proj"
673                version = "42"
674            """),
675            "setup.py": DALS("""
676                __import__('setuptools').setup(
677                    setup_requires=["foo"],
678                    py_modules = ["hello", "world"]
679                )
680            """),
681            'hello.py': "'hello'",
682            'world.py': "'world'",
683        }
684        path.build(files)
685        build_backend = self.get_build_backend()
686        setup_requires = build_backend.get_requires_for_build_wheel()
687        assert setup_requires == ["wheel", "foo"]
688
689    def test_dont_install_setup_requires(self, tmpdir_cwd):
690        files = {
691            'setup.py': DALS("""
692                        from setuptools import setup
693
694                        setup(
695                            name="qux",
696                            version="0.0.0",
697                            py_modules=["hello"],
698                            setup_requires=["does-not-exist >99"],
699                        )
700                    """),
701            'hello.py': DALS("""
702                    def run():
703                        print('hello')
704                    """),
705        }
706
707        path.build(files)
708
709        build_backend = self.get_build_backend()
710
711        dist_dir = os.path.abspath('pip-dist-info')
712        os.makedirs(dist_dir)
713
714        # does-not-exist can't be satisfied, so if it attempts to install
715        # setup_requires, it will fail.
716        build_backend.prepare_metadata_for_build_wheel(dist_dir)
717
718    _sys_argv_0_passthrough = {
719        'setup.py': DALS("""
720            import os
721            import sys
722
723            __import__('setuptools').setup(
724                name='foo',
725                version='0.0.0',
726            )
727
728            sys_argv = os.path.abspath(sys.argv[0])
729            file_path = os.path.abspath('setup.py')
730            assert sys_argv == file_path
731            """)
732    }
733
734    def test_sys_argv_passthrough(self, tmpdir_cwd):
735        path.build(self._sys_argv_0_passthrough)
736        build_backend = self.get_build_backend()
737        with pytest.raises(AssertionError):
738            build_backend.build_sdist("temp")
739
740    @pytest.mark.parametrize('build_hook', ('build_sdist', 'build_wheel'))
741    def test_build_with_empty_setuppy(self, build_backend, build_hook):
742        files = {'setup.py': ''}
743        path.build(files)
744
745        with pytest.raises(
746                ValueError,
747                match=re.escape('No distribution was found.')):
748            getattr(build_backend, build_hook)("temp")
749
750
751class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
752    backend_name = 'setuptools.build_meta:__legacy__'
753
754    # build_meta_legacy-specific tests
755    def test_build_sdist_relative_path_import(self, tmpdir_cwd):
756        # This must fail in build_meta, but must pass in build_meta_legacy
757        path.build(self._relative_path_import_files)
758
759        build_backend = self.get_build_backend()
760        build_backend.build_sdist("temp")
761
762    def test_sys_argv_passthrough(self, tmpdir_cwd):
763        path.build(self._sys_argv_0_passthrough)
764
765        build_backend = self.get_build_backend()
766        build_backend.build_sdist("temp")
767