Repository: tox-dev/tox

Issue:
Testing with `--installpkg somewheel.whl` broken (Regression in 3.5.0)
Tox 3.5.0 seems to break my Travis tests for Prequ. I have set the Travis configuration so that it runs `python setup.py bdist_wheel` and then runs as `tox -v --installpkg dist/*.whl`.  This worked fine with earlier Tox versions (<=3.4.0).

With Tox 3.5.0 my Travis job started to break with an exception raised from this line:

https://github.com/tox-dev/tox/blob/5f2535f1a697fe4bfbec571407a742615b410e24/src/tox/package.py#L64

The exception was `AttributeError: 'str' object has no attribute 'basename'`.  Full trace back is available in the build log: https://travis-ci.org/suutari/prequ/jobs/438550460

I think the error happens because `acquire_package` returns a bare `str` if `--instalpkg` is used, but otherwise it seems to return a `_path.local.LocalPath` instance. The failing code in `create_session_view` seems to assume (since commit 6538f74fd7) that the `package` is a a rich `Path` object rather than bare string.

Discussion:

Patch:
diff --git a/docs/changelog/1042.bugfix.rst b/docs/changelog/1042.bugfix.rst
new file mode 100644
index 00000000..53bde7e2
--- /dev/null
+++ b/docs/changelog/1042.bugfix.rst
@@ -0,0 +1,4 @@
+session packages are now put inside a numbered directory (instead of prefix numbering it,
+because pip fails when wheels are not named according to
+`PEP-491 <https://www.python.org/dev/peps/pep-0491/#id9>`_, and prefix numbering messes with this)
+- by user:`gaborbernat`
diff --git a/src/tox/package.py b/src/tox/package.py
index 29548c65..45c88829 100644
--- a/src/tox/package.py
+++ b/src/tox/package.py
@@ -55,24 +55,30 @@ def create_session_view(package, temp_dir, report):
     """
     if not package:
         return package
-    temp_dir.ensure(dir=True)
+    package_dir = temp_dir.join("package")
+    package_dir.ensure(dir=True)
 
-    # we'll prefix it with a unique number, note adding as suffix can cause conflicts
-    # with tools that check the files extension (e.g. pip)
-    exists = [
-        i.basename[: -len(package.basename) - 1]
-        for i in temp_dir.listdir(fil="*-{}".format(package.basename))
-    ]
+    # we'll number the active instances, and use the max value as session folder for a new build
+    # note we cannot change package names as PEP-491 (wheel binary format)
+    # is strict about file name structure
+    exists = [i.basename for i in package_dir.listdir()]
     file_id = max(chain((0,), (int(i) for i in exists if six.text_type(i).isnumeric())))
-    session_package = temp_dir.join("{}-{}".format(file_id + 1, package.basename))
+
+    session_dir = package_dir.join(str(file_id + 1))
+    session_dir.ensure(dir=True)
+    session_package = session_dir.join(package.basename)
 
     # if we can do hard links do that, otherwise just copy
-    operation = "links"
+    links = False
     if hasattr(os, "link"):
-        os.link(str(package), str(session_package))
-    else:
-        operation = "copied"
+        try:
+            os.link(str(package), str(session_package))
+            links = True
+        except (OSError, NotImplementedError):
+            pass
+    if not links:
         package.copy(session_package)
+    operation = "links" if links else "copied"
     common = session_package.common(package)
     report.verbosity1(
         "package {} {} to {} ({})".format(
diff --git a/src/tox/session.py b/src/tox/session.py
index 4fb7cb8d..f847aff1 100644
--- a/src/tox/session.py
+++ b/src/tox/session.py
@@ -459,6 +459,7 @@ class Session:
                 ):
                     self.report.verbosity2("cleanup {}".format(tox_env.package))
                     tox_env.package.remove()
+                    py.path.local(tox_env.package.dirname).remove(ignore_errors=True)
 
     def _copyfiles(self, srcdir, pathlist, destdir):
         for relpath in pathlist:


Test patch:
diff --git a/tests/unit/test_package.py b/tests/unit/test_package.py
index e4c39a48..46014b5b 100644
--- a/tests/unit/test_package.py
+++ b/tests/unit/test_package.py
@@ -54,13 +54,13 @@ def test_make_sdist_distshare(tmpdir, initproj):
     package, dist = get_package(session)
     assert package.check()
     assert package.ext == ".zip"
-    assert package == config.temp_dir.join(package.basename)
+    assert package == config.temp_dir.join("package", "1", package.basename)
 
-    assert dist == config.distdir.join(package.basename[len("1-") :])
+    assert dist == config.distdir.join(package.basename)
     assert dist.check()
     assert os.stat(str(dist)).st_ino == os.stat(str(package)).st_ino
 
-    sdist_share = config.distshare.join(package.basename[len("1-") :])
+    sdist_share = config.distshare.join(package.basename)
     assert sdist_share.check()
     assert sdist_share.read("rb") == dist.read("rb"), (sdist_share, package)
 
@@ -126,7 +126,7 @@ def test_separate_sdist(cmd, initproj, tmpdir):
     assert msg in result.out, result.out
     operation = "copied" if not hasattr(os, "link") else "links"
     msg = "package {} {} to {}".format(
-        os.sep.join(("pkg123", ".tox", ".tmp", "1-pkg123-0.7.zip")),
+        os.sep.join(("pkg123", ".tox", ".tmp", "package", "1", "pkg123-0.7.zip")),
         operation,
         os.sep.join(("distshare", "pkg123-0.7.zip")),
     )