Repository: conan-io/conan
Pull request #17659 (2025-01-29 13:03:53)

Allow access to `extension_properties` for dependencies
Changelog: Feature: Add `extension_properties` access to conanfile dependencies
Docs: TODO

This can help with implementing transitiveness of the properties from within the plugins, see the linked issue for more context

Closes https://github.com/conan-io/conan/issues/17657


Patch:
diff --git a/codecov.yml b/codecov.yml
index 561bf760e..f418c3d13 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,1 +1,3 @@
 comment: false # Disable codecov PR comments -> leave only the checks
+ignore:
+  - "test/performance"
diff --git a/conan/internal/default_settings.py b/conan/internal/default_settings.py
index 0c6f0db6b..e85390b09 100644
--- a/conan/internal/default_settings.py
+++ b/conan/internal/default_settings.py
@@ -113,7 +113,7 @@ compiler:
                     "13", "13.1", "13.2", "13.3",
                     "14", "14.1", "14.2"]
         libcxx: [libstdc++, libstdc++11]
-        threads: [null, posix, win32]  # Windows MinGW
+        threads: [null, posix, win32, mcf]  # Windows MinGW
         exception: [null, dwarf2, sjlj, seh]  # Windows MinGW
         cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23, 26, gnu26]
         cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
diff --git a/conan/internal/model/conanfile_interface.py b/conan/internal/model/conanfile_interface.py
index 2305821ea..8a8c85627 100644
--- a/conan/internal/model/conanfile_interface.py
+++ b/conan/internal/model/conanfile_interface.py
@@ -138,3 +138,7 @@ class ConanFileInterface:
     @property
     def url(self):
         return self._conanfile.url
+
+    @property
+    def extension_properties(self):
+        return getattr(self._conanfile, "extension_properties", {})
diff --git a/conan/internal/model/cpp_info.py b/conan/internal/model/cpp_info.py
index 05f9af4c2..05e982545 100644
--- a/conan/internal/model/cpp_info.py
+++ b/conan/internal/model/cpp_info.py
@@ -649,7 +649,7 @@ class CppInfo:
         return getattr(self._package, attr)
 
     def __setattr__(self, attr, value):
-        if attr in ("components", "default_components", "_package", "_aggregated"):
+        if attr in ("components", "default_components", "_package", "_aggregated", "required_components"):
             super(CppInfo, self).__setattr__(attr, value)
         else:
             setattr(self._package, attr, value)
diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py
index 433c30e2d..66a2a43a9 100644
--- a/conan/tools/files/files.py
+++ b/conan/tools/files/files.py
@@ -360,33 +360,37 @@ def untargz(filename, destination=".", pattern=None, strip_root=False, extract_f
         f = getattr(tarfile, f"{extract_filter}_filter", None) if extract_filter else None
         tarredgzippedFile.extraction_filter = f or (lambda member_, _: member_)
         if not pattern and not strip_root:
+            # Simple case: extract everything
             tarredgzippedFile.extractall(destination)
         else:
-            members = tarredgzippedFile.getmembers()
-
-            if pattern:
-                members = list(filter(lambda m: fnmatch(m.name, pattern),
-                                      tarredgzippedFile.getmembers()))
-            if strip_root:
-                names = [member.name.replace("\\", "/") for member in members]
-                common_folder = os.path.commonprefix(names).split("/", 1)[0]
-                if not common_folder and len(names) > 1:
-                    raise ConanException("The tgz file contains more than 1 folder in the root")
-                if len(names) == 1 and len(names[0].split("/", 1)) == 1:
-                    raise ConanException("The tgz file contains a file in the root")
-                # Remove the directory entry if present
-                members = [m for m in members if m.name != common_folder]
-                for member in members:
+            # Read members one by one and process them
+            common_folder = None
+            for member in tarredgzippedFile:
+                if pattern and not fnmatch(member.name, pattern):
+                    continue  # Skip files that don’t match the pattern
+
+                if strip_root:
                     name = member.name.replace("\\", "/")
-                    member.name = name.split("/", 1)[1]
+                    if not common_folder:
+                        splits = name.split("/", 1)
+                        # First case for a plain folder in the root
+                        if member.isdir() or len(splits) > 1:
+                            common_folder = splits[0]  # Find the root folder
+                        else:
+                            raise ConanException("Can't untar a tgz containing files in the root with strip_root enabled")
+                    if not name.startswith(common_folder):
+                        raise ConanException("The tgz file contains more than 1 folder in the root")
+
+                    # Adjust the member's name for extraction
+                    member.name = name[len(common_folder) + 1:]
                     member.path = member.name
-                    if member.linkpath.startswith(common_folder):
+                    if member.linkpath and member.linkpath.startswith(common_folder):
                         # https://github.com/conan-io/conan/issues/11065
-                        linkpath = member.linkpath.replace("\\", "/")
-                        member.linkpath = linkpath.split("/", 1)[1]
+                        member.linkpath = member.linkpath[len(common_folder) + 1:].replace("\\", "/")
                         member.linkname = member.linkpath
-            tarredgzippedFile.extractall(destination, members=members)
-
+                # Extract file one by one, avoiding `extractall()` with members parameter:
+                # This will avoid a first whole file read resulting in a performant improvement around 25%
+                tarredgzippedFile.extract(member, path=destination)
 
 def check_sha1(conanfile, file_path, signature):
     """
diff --git a/conan/tools/sbom/__init__.py b/conan/tools/sbom/__init__.py
index e69de29bb..1f1641e12 100644
--- a/conan/tools/sbom/__init__.py
+++ b/conan/tools/sbom/__init__.py
@@ -0,0 +1,1 @@
+from conan.tools.sbom.cyclonedx import cyclonedx_1_4
diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py
index a875833df..967e45215 100644
--- a/conan/tools/sbom/cyclonedx.py
+++ b/conan/tools/sbom/cyclonedx.py
@@ -1,5 +1,6 @@
 
-def cyclonedx_1_4(graph, **kwargs):
+
+def cyclonedx_1_4(graph, add_build=False, add_tests=False, **kwargs):
     """
     (Experimental) Generate cyclone 1.4 sbom with json format
     """
@@ -9,8 +10,7 @@ def cyclonedx_1_4(graph, **kwargs):
 
     has_special_root_node = not (getattr(graph.root.ref, "name", False) and getattr(graph.root.ref, "version", False) and getattr(graph.root.ref, "revision", False))
     special_id = str(uuid.uuid4())
-
-    components = [node for node in graph.nodes]
+    components = [node for node in graph.nodes if (node.context == "host" or add_build) and (not node.test or add_tests)]
     if has_special_root_node:
         components = components[1:]
 
@@ -22,7 +22,9 @@ def cyclonedx_1_4(graph, **kwargs):
         dependencies.append(deps)
     for c in components:
         deps = {"ref": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}"}
-        depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in c.dependencies]
+        dep = [d for d in c.dependencies if (d.dst.context == "host" or add_build) and (not d.dst.test or add_tests)]
+
+        depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in dep]
         if depends_on:
             deps["dependsOn"] = depends_on
         dependencies.append(deps)
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index 6e0ad9e27..c7bcb6ffe 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -260,15 +260,18 @@ class Git:
         files = files.splitlines()
         return files
 
-    def coordinates_to_conandata(self):
+    def coordinates_to_conandata(self, repository=False):
         """
         Capture the "url" and "commit" from the Git repo, calling ``get_url_and_commit()``, and then
         store those in the ``conandata.yml`` under the "scm" key. This information can be
         used later to clone and checkout the exact source point that was used to create this
         package, and can be useful even if the recipe uses ``exports_sources`` as mechanism to
         embed the sources.
+
+        :param repository: By default gets the commit of the defined folder, use repository=True to get
+                     the commit of the repository instead.
         """
-        scm_url, scm_commit = self.get_url_and_commit()
+        scm_url, scm_commit = self.get_url_and_commit(repository=repository)
         update_conandata(self._conanfile, {"scm": {"commit": scm_commit, "url": scm_url}})
 
     def checkout_from_conandata_coordinates(self):
diff --git a/conans/client/rest/file_uploader.py b/conans/client/rest/file_uploader.py
index 0b831d2da..5005a5c14 100644
--- a/conans/client/rest/file_uploader.py
+++ b/conans/client/rest/file_uploader.py
@@ -97,7 +97,7 @@ class FileUploader(object):
 
 
 class FileProgress(io.FileIO):
-    def __init__(self, path: str, msg: str = "Uploading", interval: float = 1, *args, **kwargs):
+    def __init__(self, path: str, msg: str = "Uploading", interval: float = 10, *args, **kwargs):
         super().__init__(path, *args, **kwargs)
         self._size = os.path.getsize(path)
         self._filename = os.path.basename(path)
diff --git a/conans/client/rest/rest_client_v2.py b/conans/client/rest/rest_client_v2.py
index 97ff28015..45ca3f192 100644
--- a/conans/client/rest/rest_client_v2.py
+++ b/conans/client/rest/rest_client_v2.py
@@ -277,27 +277,6 @@ class RestV2Methods:
             result.update({fn: os.path.join(dest_folder, fn) for fn in files})
         return result
 
-    @staticmethod
-    def _is_dir(path, files):
-        if path == ".":
-            return True
-        for the_file in files["files"]:
-            if path == the_file:
-                return False
-            elif the_file.startswith(path):
-                return True
-        raise NotFoundException("The specified path doesn't exist")
-
-    @staticmethod
-    def _list_dir_contents(path, files):
-        ret = []
-        for the_file in files["files"]:
-            if path == "." or the_file.startswith(path):
-                tmp = the_file[len(path) - 1:].split("/", 1)[0]
-                if tmp not in ret:
-                    ret.append(tmp)
-        return sorted(ret)
-
     def _upload_files(self, files, urls, ref):
         failed = []
         uploader = FileUploader(self.requester, self.verify_ssl, self._config)
diff --git a/conans/client/rest/rest_routes.py b/conans/client/rest/rest_routes.py
index 7dab1f077..b2059162d 100644
--- a/conans/client/rest/rest_routes.py
+++ b/conans/client/rest/rest_routes.py
@@ -31,22 +31,10 @@ class RestRoutes(object):
     def recipe_revision_file(self):
         return '%s/files/{path}' % self.recipe_revision
 
-    @property
-    def packages(self):
-        return '%s/packages' % self.recipe
-
     @property
     def packages_revision(self):
         return '%s/packages' % self.recipe_revision
 
-    @property
-    def package(self):
-        return '%s/{package_id}' % self.packages
-
-    @property
-    def package_files(self):
-        return '%s/files' % self.package
-
     @property
     def package_recipe_revision(self):
         """Route for a package specifying the recipe revision but not the package revision"""


Test patch:
diff --git a/.ci/docker/conan-tests b/.ci/docker/conan-tests
index b130609b2..79f43a3b6 100644
--- a/.ci/docker/conan-tests
+++ b/.ci/docker/conan-tests
@@ -14,6 +14,7 @@ ENV PY36=3.6.15 \
     CMAKE_3_17=/usr/share/cmake-3.17.5/bin/cmake \
     CMAKE_3_19=/usr/share/cmake-3.19.7/bin/cmake \
     CMAKE_3_23=/usr/share/cmake-3.23.5/bin/cmake \
+    CMAKE_3_27=/usr/share/cmake-3.27.9/bin/cmake \
     GCC_9=/usr/bin/gcc-9 \
     GXX_9=/usr/bin/g++-9 \
     GCC_11=/usr/bin/gcc-11 \
@@ -115,11 +116,14 @@ RUN wget https://github.com/Kitware/CMake/releases/download/v3.15.7/cmake-3.15.7
     tar -xvzf cmake-3.19.7-Linux-x86_64.tar.gz && mv cmake-3.19.7-Linux-x86_64 /usr/share/cmake-3.19.7 && \
     wget https://github.com/Kitware/CMake/releases/download/v3.23.5/cmake-3.23.5-Linux-x86_64.tar.gz   && \
     tar -xvzf cmake-3.23.5-Linux-x86_64.tar.gz && mv cmake-3.23.5-linux-x86_64/ /usr/share/cmake-3.23.5 && \
+    wget https://github.com/Kitware/CMake/releases/download/v3.27.9/cmake-3.27.9-Linux-x86_64.tar.gz   && \
+    tar -xvzf cmake-3.27.9-Linux-x86_64.tar.gz && mv cmake-3.27.9-linux-x86_64/ /usr/share/cmake-3.27.9 && \
     update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_15 10 && \
     update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_16 20 && \
     update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_17 30 && \
     update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_19 40 && \
-    update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_23 50
+    update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_23 50 && \
+    update-alternatives --install /usr/bin/cmake cmake $CMAKE_3_27 60
 
 RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 10 && \
     update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 30 && \
diff --git a/.github/workflows/osx-tests.yml b/.github/workflows/osx-tests.yml
index 065afbc33..13eab0aab 100644
--- a/.github/workflows/osx-tests.yml
+++ b/.github/workflows/osx-tests.yml
@@ -53,6 +53,7 @@ jobs:
             ~/Applications/CMake/3.15.7
             ~/Applications/CMake/3.19.7
             ~/Applications/CMake/3.23.5
+            ~/Applications/CMake/3.27.9
             ~/Applications/bazel/6.5.0
             ~/Applications/bazel/7.4.1
             ~/Applications/bazel/8.0.0
@@ -81,7 +82,7 @@ jobs:
         if: steps.cache-tools.outputs.cache-hit != 'true'
         run: |
           set -e
-          CMAKE_PRECOMP_VERSIONS=("3.19.7" "3.23.5")
+          CMAKE_PRECOMP_VERSIONS=("3.19.7" "3.23.5" "3.27.9")
           for version in "${CMAKE_PRECOMP_VERSIONS[@]}"; do
             echo "Downloading and installing precompiled universal CMake version ${version}..."
             wget -q --no-check-certificate https://cmake.org/files/v${version%.*}/cmake-${version}-macos-universal.tar.gz
diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml
index 35214637a..71775b251 100644
--- a/.github/workflows/win-tests.yml
+++ b/.github/workflows/win-tests.yml
@@ -158,15 +158,16 @@ jobs:
             C:\tools\cmake\3.15.7
             C:\tools\cmake\3.19.7
             C:\tools\cmake\3.23.5
+            C:\tools\cmake\3.27.9
             C:\tools\bazel\6.5.0
             C:\tools\bazel\7.4.1
             C:\tools\bazel\8.0.0
           key: ${{ runner.os }}-conan-tools-cache
 
-      - name: Build CMake old versions of CMake
+      - name: Install CMake versions
         if: steps.cache-tools.outputs.cache-hit != 'true'
         run: |
-          $CMAKE_BUILD_VERSIONS = "3.15.7", "3.19.7"
+          $CMAKE_BUILD_VERSIONS = "3.15.7", "3.19.7", "3.23.5", "3.27.9"
           foreach ($version in $CMAKE_BUILD_VERSIONS) {
             Write-Host "Downloading CMake version $version for Windows..."
             $destination = "C:\tools\cmake\$version"
@@ -174,24 +175,12 @@ jobs:
                 New-Item -Path $destination -ItemType Directory
             }
             $major_minor_version = ($version -split '\.')[0..1] -join '.'
-            $url = "https://cmake.org/files/v$major_minor_version/cmake-$version-win64-x64.zip"
-            $zipFile = "cmake-$version-windows-x86_64.zip"
-            Invoke-WebRequest -Uri $url -OutFile $zipFile
-            Expand-Archive -Path $zipFile -DestinationPath $destination -Force
-            Remove-Item $zipFile
-          }
-
-      - name: Install modern CMake versions
-        if: steps.cache-tools.outputs.cache-hit != 'true'
-        run: |
-          $CMAKE_BUILD_VERSIONS = "3.23.5"
-          foreach ($version in $CMAKE_BUILD_VERSIONS) {
-            $destination = "C:\tools\cmake\$version"
-            if (-not (Test-Path $destination)) {
-                New-Item -Path $destination -ItemType Directory
+            if ( $major_minor_version -eq "3.15" -or $major_minor_version -eq "3.19" ) {
+                $arch = "win64-x64"
+            } else {
+                $arch = "windows-x86_64"
             }
-            $major_minor_version = ($version -split '\.')[0..1] -join '.'
-            $url = "https://cmake.org/files/v$major_minor_version/cmake-$version-windows-x86_64.zip"
+            $url = "https://cmake.org/files/v$major_minor_version/cmake-$version-$arch.zip"
             $zipFile = "cmake-$version-windows-x86_64.zip"
             Invoke-WebRequest -Uri $url -OutFile $zipFile
             Expand-Archive -Path $zipFile -DestinationPath $destination -Force
diff --git a/conan/test/assets/__init__.py b/conan/test/assets/__init__.py
index c7f40f86e..e69de29bb 100644
--- a/conan/test/assets/__init__.py
+++ b/conan/test/assets/__init__.py
@@ -1,16 +0,0 @@
-import os
-import shutil
-
-
-def copy_assets(src_folder, dst_folder, assets=None):
-    assets_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files")
-    if src_folder:
-        src_folder = os.path.join(assets_path, src_folder)
-    assets = assets or os.listdir(src_folder)
-    for asset in assets:
-        s = os.path.join(src_folder, asset)
-        d = os.path.join(dst_folder, asset)
-        if os.path.isdir(s):
-            shutil.copytree(s, d)
-        else:
-            shutil.copy2(s, d)
diff --git a/conan/test/assets/visual_project_files.py b/conan/test/assets/visual_project_files.py
index 5db7b7cec..9b9f5ceb5 100644
--- a/conan/test/assets/visual_project_files.py
+++ b/conan/test/assets/visual_project_files.py
@@ -245,47 +245,9 @@ int main()
 }
 '''
 
-main_file_17_2015 = '''
-//available in 2015 with c++17
-#include <iostream>
-
-namespace test::mytest {
-struct mystruct {};
-};
-int main(){
-test::mytest::mystruct t;
-}
-'''
-
-
-main_file_17_2017 = '''
-//only available in visual 2017 with c++17
-#include <iostream>
-
-auto hello(){
-    return 2;
-}
-
-int main()
-{
-    if constexpr(true)
-        std::cout << "Hello World!" << std::endl;
-    return 0;
-}
-'''
-
-
-def get_vs_project_files(std=False):
-    if not std:
-        main = main_file
-    elif std == "cpp17_2015":
-        main = main_file_17_2015
-    elif std == "cpp17_2017":
-        main = main_file_17_2017
-    else:
-        raise Exception("Incorrect std")
 
+def get_vs_project_files():
     return {"MyProject.sln": sln_file,
             "MyProject/MyProject.vcxproj": vcxproj_file,
             "MyProject/MyProject.vcxproj.filters": filters_file,
-            "MyProject/main.cpp": main}
+            "MyProject/main.cpp": main_file}
diff --git a/conan/test/utils/mocks.py b/conan/test/utils/mocks.py
index 9638b4ad8..cd4e9f45e 100644
--- a/conan/test/utils/mocks.py
+++ b/conan/test/utils/mocks.py
@@ -9,25 +9,6 @@ from conan.internal.model.layout import Folders, Infos
 from conan.internal.model.options import Options
 
 
-class LocalDBMock(object):
-
-    def __init__(self, user=None, access_token=None, refresh_token=None):
-        self.user = user
-        self.access_token = access_token
-        self.refresh_token = refresh_token
-
-    def get_login(self, _):
-        return self.user, self.access_token, self.refresh_token
-
-    def get_username(self, _):
-        return self.user
-
-    def store(self, user, access_token, refresh_token, _):
-        self.user = user
-        self.access_token = access_token
-        self.refresh_token = refresh_token
-
-
 class RedirectedInputStream:
     """
     Mock for testing. If get_username or get_password is requested will raise
@@ -67,19 +48,6 @@ class MockSettings(object):
         return defaultdict(lambda: [])
 
 
-class MockCppInfo(object):
-    def __init__(self):
-        self.bin_paths = []
-        self.lib_paths = []
-        self.include_paths = []
-        self.libs = []
-        self.cflags = []
-        self.cxxflags = []
-        self.defines = []
-        self.frameworks = []
-        self.framework_paths = []
-
-
 class ConanFileMock(ConanFile):
     def __init__(self, settings=None, options=None, runner=None, display_name=""):
         self.display_name = display_name
diff --git a/conan/test/utils/tools.py b/conan/test/utils/tools.py
index b143a7502..d0c44a9ea 100644
--- a/conan/test/utils/tools.py
+++ b/conan/test/utils/tools.py
@@ -41,7 +41,6 @@ from conan.api.model import PkgReference
 from conan.internal.model.profile import Profile
 from conan.api.model import RecipeReference
 from conan.internal.model.settings import Settings
-from conan.test.assets import copy_assets
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.artifactory import ArtifactoryServer
 from conan.test.utils.mocks import RedirectedInputStream
@@ -661,9 +660,6 @@ class TestClient:
     def save_home(self, files):
         self.save(files, path=self.cache_folder)
 
-    def copy_assets(self, origin_folder, assets=None):
-        copy_assets(origin_folder, self.current_folder, assets)
-
     # Higher level operations
     def remove_all(self):
         self.run("remove '*' -c")
diff --git a/test/conftest.py b/test/conftest.py
index 12a683f94..662e73629 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -82,8 +82,10 @@ tools_locations = {
                      'Darwin': '/Users/runner/Applications/CMake/3.23.5/bin',
                      'Linux': "/usr/share/cmake-3.23.5/bin"}
         },
-        "3.28": {
-            "disabled": True  # TODO: Still not in CI
+        "3.27": {
+            "path": {'Windows': 'C:/tools/cmake/3.27.9/cmake-3.27.9-windows-x86_64/bin',
+                     'Darwin': '/Users/runner/Applications/CMake/3.27.9/bin',
+                     'Linux': "/usr/share/cmake-3.27.9/bin"}
         }
     },
     'ninja': {
diff --git a/test/functional/sbom/test_cyclonedx.py b/test/functional/sbom/test_cyclonedx.py
index 8aafd1945..3c1b80a0b 100644
--- a/test/functional/sbom/test_cyclonedx.py
+++ b/test/functional/sbom/test_cyclonedx.py
@@ -13,10 +13,10 @@ import json
 import os
 from conan.errors import ConanException
 from conan.api.output import ConanOutput
-from conan.tools.sbom.cyclonedx import cyclonedx_1_4
+from conan.tools.sbom import cyclonedx_1_4
 
 def post_package(conanfile):
-    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph)
+    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph, add_build=%s, add_tests=%s)
     metadata_folder = conanfile.package_metadata_folder
     file_name = "sbom.cdx.json"
     with open(os.path.join(metadata_folder, file_name), 'w') as f:
@@ -28,14 +28,28 @@ def post_package(conanfile):
 def hook_setup_post_package():
     tc = TestClient()
     hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
-    save(hook_path, sbom_hook_post_package)
+    save(hook_path, sbom_hook_post_package % ("True", "True"))
+    return tc
+
+@pytest.fixture()
+def hook_setup_post_package_no_tool_requires():
+    tc = TestClient()
+    hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
+    save(hook_path, sbom_hook_post_package % ("False", "True"))
+    return tc
+
+@pytest.fixture()
+def hook_setup_post_package_no_test():
+    tc = TestClient()
+    hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
+    save(hook_path, sbom_hook_post_package % ("True", "False"))
     return tc
 
 @pytest.fixture()
 def hook_setup_post_package_tl(transitive_libraries):
     tc = transitive_libraries
     hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
-    save(hook_path, sbom_hook_post_package)
+    save(hook_path, sbom_hook_post_package % ("True", "True"))
     return tc
 
 
@@ -65,6 +79,55 @@ def test_sbom_generation_skipped_dependencies(hook_setup_post_package):
     # A skipped dependency also shows up in the sbom
     assert "pkg:conan/dep@1.0?rref=6a99f55e933fb6feeb96df134c33af44" in content
 
+def test_sbom_generation_no_tool_requires(hook_setup_post_package_no_tool_requires):
+    tc = hook_setup_post_package_no_tool_requires
+    tc.save({"app/conanfile.py": GenConanfile("app", "1.0")
+                                .with_package_type("application"),
+             "conanfile.py": GenConanfile("foo", "1.0").with_tool_requires("app/1.0")})
+    tc.run("create app")
+    tc.run("create .")
+    create_layout = tc.created_layout()
+
+    cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+    content = tc.load(cyclone_path)
+
+    assert "pkg:conan/app" not in content
+
+def test_sbom_generation_transitive_test_requires(hook_setup_post_package_no_test):
+    tc = hook_setup_post_package_no_test
+    tc.save({"test_re/conanfile.py": GenConanfile("test_re", "1.0"),
+             "app/conanfile.py": GenConanfile("app", "1.0")
+                                .with_package_type("application")
+                                .with_test_requires("test_re/1.0"),
+             "conanfile.py": GenConanfile("foo", "1.0").with_tool_requires("app/1.0")})
+    tc.run("create test_re")
+
+    tc.run("create app")
+    create_layout = tc.created_layout()
+    cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+    content = tc.load(cyclone_path)
+    assert "pkg:conan/test_re@1.0" not in content
+
+    tc.run("create .")
+    create_layout = tc.created_layout()
+    cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+    content = tc.load(cyclone_path)
+    assert "pkg:conan/test_re@1.0" not in content
+
+def test_sbom_generation_dependency_test_require(hook_setup_post_package_no_test):
+    tc = hook_setup_post_package_no_test
+    tc.save({"special/conanfile.py": GenConanfile("special", "1.0"),
+             "foo/conanfile.py": GenConanfile("foo", "1.0")
+            .with_test_requires("special/1.0"),
+             "conanfile.py": GenConanfile("bar", "1.0").with_tool_requires("foo/1.0").with_require("special/1.0")})
+    tc.run("create special")
+    tc.run("create foo")
+
+    tc.run("create .")
+    create_layout = tc.created_layout()
+    cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+    content = tc.load(cyclone_path)
+    assert "pkg:conan/special@1.0" in content
 
 # Using the sbom tool with "conan install"
 sbom_hook_post_generate = """
@@ -72,7 +135,7 @@ import json
 import os
 from conan.errors import ConanException
 from conan.api.output import ConanOutput
-from conan.tools.sbom.cyclonedx import cyclonedx_1_4
+from conan.tools.sbom import cyclonedx_1_4
 
 def post_generate(conanfile):
     sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph)
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 75129834c..cacc5eead 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -638,7 +638,7 @@ class TestWinSDKVersion:
         assert "Conan toolchain: CMAKE_GENERATOR_PLATFORM=x64" in client.out
         assert "Conan toolchain: CMAKE_GENERATOR_PLATFORM=x64,version" not in client.out
 
-    @pytest.mark.tool("cmake", "3.28")
+    @pytest.mark.tool("cmake", "3.27")
     @pytest.mark.tool("visual_studio", "17")
     def test_cmake_toolchain_winsdk_version2(self):
         # https://github.com/conan-io/conan/issues/15372
diff --git a/test/functional/toolchains/microsoft/test_msbuild.py b/test/functional/toolchains/microsoft/test_msbuild.py
index c476e07c0..0754e2e33 100644
--- a/test/functional/toolchains/microsoft/test_msbuild.py
+++ b/test/functional/toolchains/microsoft/test_msbuild.py
@@ -488,12 +488,12 @@ class TestWin:
         check_vs_runtime("Release/MyApp.exe", client, ide_version, build_type="Release",
                          static_runtime=static_runtime)
 
-    @pytest.mark.tool("cmake")
-    @pytest.mark.tool("visual_studio", "16")
+    @pytest.mark.tool("cmake", "3.23")
+    @pytest.mark.tool("visual_studio", "17")
     def test_toolchain_win_debug(self):
         client = TestClient(path_with_spaces=False)
         settings = [("compiler",  "msvc"),
-                    ("compiler.version",  "192"),
+                    ("compiler.version",  "193"),
                     ("compiler.runtime",  "dynamic"),
                     ("build_type",  "Debug"),
                     ("arch",  "x86_64")]
@@ -514,27 +514,27 @@ class TestWin:
         # Run the configure corresponding to this test case
         client.run("build . %s" % (settings, ))
         assert "conanfile.py: MSBuildToolchain created conantoolchain_debug_x64.props" in client.out
-        assert f"conanvcvars.bat: Activating environment Visual Studio 16" in client.out
+        assert f"conanvcvars.bat: Activating environment Visual Studio 17" in client.out
         assert "[vcvarsall.bat] Environment initialized for: 'x64'" in client.out
         self._run_app(client, "x64", "Debug")
         assert "Hello World Debug" in client.out
-        check_exe_run(client.out, "main", "msvc", "192", "Debug", "x86_64", "14",
+        check_exe_run(client.out, "main", "msvc", "19", "Debug", "x86_64", "14",
                       {"DEFINITIONS_BOTH": 'True',
                        "DEFINITIONS_BOTH2": "True",
                        "DEFINITIONS_BOTH_INT": "123",
                        "DEFINITIONS_CONFIG": 'Debug',
                        "DEFINITIONS_CONFIG2": 'Debug',
                        "DEFINITIONS_CONFIG_INT": "234"})
-        check_vs_runtime("x64/Debug/MyApp.exe", client, "16", build_type="Debug")
+        check_vs_runtime("x64/Debug/MyApp.exe", client, "17", build_type="Debug")
 
-    @pytest.mark.tool("cmake")
-    @pytest.mark.tool("visual_studio", "16")
+    @pytest.mark.tool("cmake", "3.23")
+    @pytest.mark.tool("visual_studio", "17")
     def test_toolchain_win_multi(self):
-        ide_version = "16"
+        ide_version = "17"
         client = TestClient(path_with_spaces=False)
 
         settings = [("compiler", "msvc"),
-                    ("compiler.version", "192"),
+                    ("compiler.version", "193"),
                     ("compiler.cppstd", "17"),
                     ("compiler.runtime", "static")]
 
@@ -582,7 +582,7 @@ class TestWin:
             assert "[vcvarsall.bat] Environment initialized for: 'x64'" in client.out
 
             self._run_app(client, arch, build_type, shared)
-            check_exe_run(client.out, "main", "msvc", "192", build_type, arch, "17",
+            check_exe_run(client.out, "main", "msvc", "19", build_type, arch, "17",
                           {"DEFINITIONS_BOTH": "True",
                            "DEFINITIONS_CONFIG": build_type})
 
diff --git a/test/integration/cache/backup_sources_test.py b/test/integration/cache/backup_sources_test.py
index 63c539acf..e992e2e85 100644
--- a/test/integration/cache/backup_sources_test.py
+++ b/test/integration/cache/backup_sources_test.py
@@ -16,9 +16,8 @@ from conans.util.files import save, load, rmdir, mkdir, remove
 
 class TestDownloadCacheBackupSources:
     def test_users_download_cache_summary(self):
-        def custom_download(this, url, filepath, *args, **kwargs):
-            if url.startswith("http://myback"):
-                raise NotFoundException()
+        def custom_download(this, url, filepath, *args, **kwargs):  # noqa
+            assert not url.startswith("http://myback")
             save(filepath, f"Hello, world!")
 
         with mock.patch("conans.client.downloaders.file_downloader.FileDownloader.download",
diff --git a/test/integration/cache/download_cache_test.py b/test/integration/cache/download_cache_test.py
index 5fa2cf189..3fc23d54c 100644
--- a/test/integration/cache/download_cache_test.py
+++ b/test/integration/cache/download_cache_test.py
@@ -66,12 +66,14 @@ class TestDownloadCache:
         client.run("install --requires=pkg/0.1@")
 
         # Make the cache dirty
-        for f in os.listdir(tmp_folder):
+        # The "c" internal folder must exist, it is the actual storage of blobs
+        cache_folder = os.path.join(tmp_folder, "c")
+        for f in os.listdir(cache_folder):
             # damage the file
-            path = os.path.join(tmp_folder, f)
-            if os.path.isfile(path):
-                save(path, "broken!")
-                set_dirty(path)
+            path = os.path.join(cache_folder, f)
+            assert os.path.isfile(path)
+            save(path, "broken!")
+            set_dirty(path)
 
         client.run("remove * -c")
         client.run("install --requires=pkg/0.1@")
diff --git a/test/integration/command/create_test.py b/test/integration/command/create_test.py
index 620d11f51..81d75486d 100644
--- a/test/integration/command/create_test.py
+++ b/test/integration/command/create_test.py
@@ -471,10 +471,9 @@ def test_create_format_json():
             consumer_info = n
         elif ref == hello_pkg_ref:
             hello_pkg_info = n
-        elif ref == pkg_pkg_ref:
-            pkg_pkg_info = n
         else:
-            raise Exception("Ref not found. Review the revisions hash.")
+            assert ref == pkg_pkg_ref
+            pkg_pkg_info = n
 
     # Consumer information
     assert consumer_info["recipe"] == "Cli"
@@ -775,7 +774,6 @@ def test_create_both_host_build_require():
             "test_package/conanfile.py": GenConanfile().with_build_requires("protobuf/0.1")
                                                        .with_test("pass")})
     c.run("create . -s:b build_type=Release -s:h build_type=Debug", assert_error=True)
-    print(c.out)
     # The main "host" Debug binary will be correctly build
     c.assert_listed_binary({"protobuf/0.1": ("9e186f6d94c008b544af1569d1a6368d8339efc5", "Build")})
     # But test_package will fail because of the missing "tool_require" in Release
diff --git a/test/integration/command/remote_verify_ssl_test.py b/test/integration/command/remote_verify_ssl_test.py
index 0d872d53c..7ee031084 100644
--- a/test/integration/command/remote_verify_ssl_test.py
+++ b/test/integration/command/remote_verify_ssl_test.py
@@ -1,5 +1,3 @@
-import unittest
-
 import requests
 from requests.models import Response
 
@@ -11,16 +9,17 @@ resp.status_code = 200
 resp.headers = {"Content-Type": "application/json", "X-Conan-Server-Capabilities": "revisions"}
 
 
-class RequesterMockTrue(object):
+class RequesterMockFalse:
+    expected = False
 
     def __init__(self, *args, **kwargs):
         pass
 
-    def get(self, url, *args, **kwargs):
-        assert "cacert.pem" in kwargs["verify"], "TEST FAILURE: cacert.pem not in verify kwarg"
+    def get(self, url, *args, **kwargs):  # noqa
+        assert kwargs["verify"] is self.expected
         return resp
 
-    def Session(self):
+    def Session(self):  # noqa
         return self
 
     @property
@@ -30,48 +29,34 @@ class RequesterMockTrue(object):
     def mount(self, *args, **kwargs):
         pass
 
-class RequesterMockFalse(object):
 
-    def __init__(self, *args, **kwargs):
-        pass
+class RequesterMockTrue(RequesterMockFalse):
+    expected = True
 
-    def get(self, url, *args, **kwargs):
-        assert kwargs["verify"] is False, "TEST FAILURE: verify arg is not False"
-        return resp
 
-    def Session(self):
-        return self
-
-    @property
-    def codes(self):
-        return requests.codes
-
-    def mount(self, *args, **kwargs):
-        pass
-
-
-class VerifySSLTest(unittest.TestCase):
+class TestVerifySSL:
 
     def test_verify_ssl(self):
+        c = TestClient(requester_class=RequesterMockTrue)
+        c.run("remote add myremote https://localhost --insecure")
+        c.run("remote list")
+        assert "Verify SSL: False" in c.out
 
-        self.client = TestClient(requester_class=RequesterMockTrue)
-        self.client.run("remote add myremote https://localhost --insecure")
-        self.client.run("remote list")
-        self.assertIn("Verify SSL: False", self.client.out)
-
-        self.client.run("remote update myremote --url https://localhost --secure")
-        self.client.run("remote list")
-        self.assertIn("Verify SSL: True", self.client.out)
+        c.run("remote update myremote --url https://localhost --secure")
+        c.run("remote list")
+        assert "Verify SSL: True" in c.out
 
-        self.client.run("remote remove myremote")
-        self.client.run("remote add myremote https://localhost")
-        self.client.run("remote list")
-        self.assertIn("Verify SSL: True", self.client.out)
+        c.run("remote remove myremote")
+        c.run("remote add myremote https://localhost")
+        c.run("remote list")
+        assert "Verify SSL: True" in c.out
 
         # Verify that SSL is checked in requests
-        self.client.run("search op* -r myremote")
+        c.run("search op* -r myremote")
+        assert "WARN: There are no matching recipe references" in c.out
 
         # Verify that SSL is not checked in requests
-        self.client = TestClient(requester_class=RequesterMockFalse)
-        self.client.run("remote add myremote https://localhost --insecure")
-        self.client.run("search op* -r myremote")
+        c = TestClient(requester_class=RequesterMockFalse)
+        c.run("remote add myremote https://localhost --insecure")
+        c.run("search op* -r myremote")
+        assert "WARN: There are no matching recipe references" in c.out
diff --git a/test/integration/command/remove_test.py b/test/integration/command/remove_test.py
index f9f66066a..b92eca6e5 100644
--- a/test/integration/command/remove_test.py
+++ b/test/integration/command/remove_test.py
@@ -248,14 +248,11 @@ def populated_client(_populated_client_base):
 ])
 def test_new_remove_recipes_expressions(populated_client, with_remote, data):
     r = "-r default" if with_remote else ""
-    error = data.get("error", False)
-    populated_client.run("remove {} -c {}".format(data["remove"], r), assert_error=error)
-    if not error:
-        populated_client.run(f"list {r} --format=json")
-        origin = "default" if with_remote else "Local Cache"
-        assert set(json.loads(populated_client.stdout)[origin]) == set(data["recipes"])
-    elif data.get("error_msg"):
-        assert data.get("error_msg") in populated_client.out
+    assert "error" not in data
+    populated_client.run("remove {} -c {}".format(data["remove"], r))
+    populated_client.run(f"list {r} --format=json")
+    origin = "default" if with_remote else "Local Cache"
+    assert set(json.loads(populated_client.stdout)[origin]) == set(data["recipes"])
 
 
 @pytest.mark.parametrize("with_remote", [True, False])
@@ -340,13 +337,10 @@ def test_new_remove_package_revisions_expressions(populated_client, with_remote,
     prefs = _get_revisions_packages(populated_client, bar_rrev2_release, with_remote)
     assert set(prefs) == {bar_rrev2_release_prev1, bar_rrev2_release_prev2}
 
-    error = data.get("error", False)
-    populated_client.run("remove {} -c {}".format(data["remove"], r), assert_error=error)
-    if not error:
-        prefs = _get_revisions_packages(populated_client, bar_rrev2_release, with_remote)
-        assert set(prefs) == set(data["prevs"])
-    elif data.get("error_msg"):
-        assert data.get("error_msg") in populated_client.out
+    assert "error" not in data
+    populated_client.run("remove {} -c {}".format(data["remove"], r))
+    prefs = _get_revisions_packages(populated_client, bar_rrev2_release, with_remote)
+    assert set(prefs) == set(data["prevs"])
 
 
 def test_package_query_no_package_ref(populated_client):
diff --git a/test/integration/command/upload/upload_test.py b/test/integration/command/upload/upload_test.py
index 8b4cef281..8f66f1129 100644
--- a/test/integration/command/upload/upload_test.py
+++ b/test/integration/command/upload/upload_test.py
@@ -7,7 +7,6 @@ import unittest
 from collections import OrderedDict
 
 import pytest
-import requests
 from mock import patch
 from requests import Response
 
@@ -440,12 +439,6 @@ class UploadTest(unittest.TestCase):
                 self.status_code = 401
                 self.content = b''
 
-        class ErrorApiResponse(object):
-            def __init__(self):
-                self.ok = False
-                self.status_code = 400
-                self.content = "Unsupported Conan v1 repository request for 'conan'"
-
         class ServerCapabilitiesRequester(TestRequester):
             def __init__(self, *args, **kwargs):
                 self._first_ping = True
@@ -453,17 +446,14 @@ class UploadTest(unittest.TestCase):
 
             def get(self, url, **kwargs):
                 app, url = self._prepare_call(url, kwargs)
-                if app:
-                    if url.endswith("ping") and self._first_ping:
-                        self._first_ping = False
-                        return EmptyCapabilitiesResponse()
-                    elif "hello0" in url and "1.2.1" in url and "v1" in url:
-                        return ErrorApiResponse()
-                    else:
-                        response = app.get(url, **kwargs)
-                        return TestingResponse(response)
+                assert app
+                assert ("/v1/" in url and url.endswith("ping")) or "/v2" in url
+                if url.endswith("ping") and self._first_ping:
+                    self._first_ping = False
+                    return EmptyCapabilitiesResponse()
                 else:
-                    return requests.get(url, **kwargs)
+                    response = app.get(url, **kwargs)
+                    return TestingResponse(response)
 
         server = TestServer(users={"user": "password"}, write_permissions=[("*/*@*/*", "*")])
         servers = {"default": server}
diff --git a/test/integration/conanfile/runner_test.py b/test/integration/conanfile/runner_test.py
index add796687..28ce9c9b9 100644
--- a/test/integration/conanfile/runner_test.py
+++ b/test/integration/conanfile/runner_test.py
@@ -1,20 +1,9 @@
-import os
 import textwrap
-import unittest
 
 from conan.test.utils.tools import TestClient
 
 
-class RunnerTest(unittest.TestCase):
-
-    def _install_and_build(self, client, conanfile_text):
-        files = {"conanfile.py": conanfile_text}
-        test_folder = os.path.join(client.current_folder, "test_folder")
-        self.assertFalse(os.path.exists(test_folder))
-        client.save(files)
-        client.run("install .")
-        client.run("build .")
-        return client
+class TestRunner:
 
     def test_ignore_error(self):
         conanfile = """from conan import ConanFile
@@ -26,7 +15,7 @@ class Pkg(ConanFile):
         client = TestClient()
         client.save({"conanfile.py": conanfile})
         client.run("source .")
-        self.assertIn("RETCODE True", client.out)
+        assert "RETCODE True" in client.out
 
     def test_runner_capture_output(self):
         conanfile = textwrap.dedent("""
@@ -38,7 +27,7 @@ class Pkg(ConanFile):
         client = TestClient()
         client.save({"conanfile.py": conanfile})
         client.run("source .")
-        self.assertIn("hello Conan!", client.out)
+        assert "hello Conan!" in client.out
 
     def test_custom_stream_error(self):
         # https://github.com/conan-io/conan/issues/7888
@@ -54,7 +43,7 @@ class Pkg(ConanFile):
         client = TestClient()
         client.save({"conanfile.py": conanfile})
         client.run("source .")
-        self.assertIn('conanfile.py: Buffer got msgs Hello', client.out)
+        assert 'conanfile.py: Buffer got msgs Hello' in client.out
 
     def test_custom_stream_stderr(self):
         conanfile = textwrap.dedent("""
@@ -69,4 +58,4 @@ class Pkg(ConanFile):
         client = TestClient()
         client.save({"conanfile.py": conanfile})
         client.run("source .")
-        self.assertIn('conanfile.py: Buffer got stderr msgs Hello', client.out)
+        assert 'conanfile.py: Buffer got stderr msgs Hello' in client.out
diff --git a/test/integration/environment/test_env.py b/test/integration/environment/test_env.py
index a3b506ee6..293371d28 100644
--- a/test/integration/environment/test_env.py
+++ b/test/integration/environment/test_env.py
@@ -110,14 +110,14 @@ def test_complete(client, gtest_run_true):
     client.run("install . -s:b os=Windows -s:h os=Linux --build=missing")
     # Run the BUILD environment
     if platform.system() == "Windows":
-        cmd = environment_wrap_command(ConanFileMock(),"conanbuildenv", client.current_folder, "mycmake.bat")
+        cmd = environment_wrap_command(ConanFileMock(), "conanbuildenv", client.current_folder, "mycmake.bat")
         client.run_command(cmd)
         assert "MYCMAKE=Windows!!" in client.out
         assert "MYOPENSSL=Windows!!" in client.out
 
     # Run the RUN environment
     if platform.system() != "Windows":
-        cmd = environment_wrap_command(ConanFileMock(),"conanrunenv", client.current_folder,
+        cmd = environment_wrap_command(ConanFileMock(), "conanrunenv", client.current_folder,
                                        "mygtest.sh && .{}myrunner.sh".format(os.sep))
         client.run_command(cmd, assert_error=not gtest_run_true)
         if gtest_run_true:
@@ -210,7 +210,7 @@ def test_profile_buildenv():
     client.run("install . -pr=myprofile")
     # Run the BUILD environment
     ext = "bat" if platform.system() == "Windows" else "sh"  # TODO: Decide on logic .bat vs .sh
-    cmd = environment_wrap_command(ConanFileMock(),"conanbuildenv", client.current_folder,
+    cmd = environment_wrap_command(ConanFileMock(), "conanbuildenv", client.current_folder,
                                    "mycompiler.{}".format(ext))
     client.run_command(cmd)
     assert "MYCOMPILER!!" in client.out
@@ -621,14 +621,13 @@ def test_massive_paths(num_deps):
     assert os.path.isfile(os.path.join(client.current_folder, "conanrunenv.ps1"))
     assert not os.path.isfile(os.path.join(client.current_folder, "conanrunenv.bat"))
     for i in range(num_deps):
-        cmd = environment_wrap_command(ConanFileMock(),"conanrunenv", client.current_folder,
+        cmd = environment_wrap_command(ConanFileMock(), "conanrunenv", client.current_folder,
                                        "mycompiler{}.bat".format(i))
-        if num_deps > 50:  # to be safe if we change the "num_deps" number
-            client.run_command(cmd, assert_error=True)
-            assert "is not recognized as an internal" in client.out
-        else:
-            client.run_command(cmd)
-            assert "MYTOOL {}!!".format(i) in client.out
+        # if num_deps > 50:  # to be safe if we change the "num_deps" number
+        #    client.run_command(cmd, assert_error=True)
+        #    assert "is not recognized as an internal" in client.out
+        client.run_command(cmd)
+        assert "MYTOOL {}!!".format(i) in client.out
 
     # Test .bats now
     client.save({"conanfile.py": conanfile}, clean_first=True)
@@ -636,14 +635,14 @@ def test_massive_paths(num_deps):
     assert not os.path.isfile(os.path.join(client.current_folder, "conanrunenv.ps1"))
     assert os.path.isfile(os.path.join(client.current_folder, "conanrunenv.bat"))
     for i in range(num_deps):
-        cmd = environment_wrap_command(ConanFileMock(),"conanrunenv", client.current_folder,
+        cmd = environment_wrap_command(ConanFileMock(), "conanrunenv", client.current_folder,
                                        "mycompiler{}.bat".format(i))
-        if num_deps > 50:  # to be safe if we change the "num_deps" number
-            client.run_command(cmd, assert_error=True)
-            # This also fails, but without an error message (in my terminal, it kills the terminal!)
-        else:
-            client.run_command(cmd)
-            assert "MYTOOL {}!!".format(i) in client.out
+        # if num_deps > 50:  # to be safe if we change the "num_deps" number
+        #    client.run_command(cmd, assert_error=True)
+        #    # This also fails, but without an error message (in my terminal, it kills the terminal!)
+        # else:
+        client.run_command(cmd)
+        assert "MYTOOL {}!!".format(i) in client.out
 
 
 def test_profile_build_env_spaces():
diff --git a/test/integration/graph/core/graph_manager_base.py b/test/integration/graph/core/graph_manager_base.py
index 97d85b96f..23dcdd9df 100644
--- a/test/integration/graph/core/graph_manager_base.py
+++ b/test/integration/graph/core/graph_manager_base.py
@@ -67,7 +67,7 @@ class GraphManagerTest(unittest.TestCase):
         self._cache_recipe(ref, conanfile)
 
     @staticmethod
-    def recipe_consumer(reference=None, requires=None, build_requires=None, tool_requires=None):
+    def recipe_consumer(reference=None, requires=None, build_requires=None):
         path = temp_folder()
         path = os.path.join(path, "conanfile.py")
         conanfile = GenConanfile()
@@ -80,9 +80,6 @@ class GraphManagerTest(unittest.TestCase):
         if build_requires:
             for r in build_requires:
                 conanfile.with_build_requires(r)
-        if tool_requires:
-            for r in tool_requires:
-                conanfile.with_tool_requires(r)
         save(path, str(conanfile))
         return path
 
diff --git a/test/integration/graph/core/graph_manager_test.py b/test/integration/graph/core/graph_manager_test.py
index 095dfcc70..4e226b7bb 100644
--- a/test/integration/graph/core/graph_manager_test.py
+++ b/test/integration/graph/core/graph_manager_test.py
@@ -1,7 +1,6 @@
 import pytest
 from parameterized import parameterized
 
-from conans.client.graph.graph import BINARY_SKIP
 from conans.client.graph.graph_error import GraphMissingError, GraphLoopError, GraphConflictError
 from conan.errors import ConanException
 from test.integration.graph.core.graph_manager_base import GraphManagerTest
@@ -11,24 +10,17 @@ from conan.test.utils.tools import GenConanfile
 def _check_transitive(node, transitive_deps):
     values = list(node.transitive_deps.values())
 
-    if len(values) != len(transitive_deps):
-        values = [r.require.ref for r in values]
-        raise Exception("{}: Number of deps don't match \n{}!=\n{}".format(node, values,
-                                                                           transitive_deps))
+    assert len(values) == len(transitive_deps), f"{node}: Number of deps don't match " \
+                                                f"\n{[r.require.ref for r in values]}" \
+                                                f"!=\n{transitive_deps}"
 
     for v1, v2 in zip(values, transitive_deps):
-        if v1.node is not v2[0]:
-            raise Exception(f"{v1.node}!={v2[0]}")
-        if v1.require.headers is not v2[1]:
-            raise Exception(f"{v1.node}!={v2[0]} headers")
-        if v1.require.libs is not v2[2]:
-            raise Exception(f"{v1.node}!={v2[0]} libs")
-        if v1.require.build is not v2[3]:
-            raise Exception(f"{v1.node}!={v2[0]} build")
-        if v1.require.run is not v2[4]:
-            raise Exception(f"{v1.node}!={v2[0]} run")
-        if len(v2) > 5:
-            assert v1.require.package_id_mode is v2[5]
+        assert v1.node is v2[0], f"{v1.node}!={v2[0]}"
+        assert v1.require.headers is v2[1], f"{v1.node}!={v2[0]} headers"
+        assert v1.require.libs is v2[2], f"{v1.node}!={v2[0]} libs"
+        assert v1.require.build is v2[3], f"{v1.node}!={v2[0]} build"
+        assert v1.require.run is  v2[4], f"{v1.node}!={v2[0]} run"
+        assert len(v2) <= 5
 
 
 class TestLinear(GraphManagerTest):
diff --git a/test/integration/lockfile/test_lock_requires.py b/test/integration/lockfile/test_lock_requires.py
index 200dbb534..f9e182872 100644
--- a/test/integration/lockfile/test_lock_requires.py
+++ b/test/integration/lockfile/test_lock_requires.py
@@ -477,6 +477,47 @@ class TestLockTestPackage:
             assert "dep/2.0" not in c.out
             assert "package tested" in c.out
 
+    def test_test_package_lockfile(self):
+        c = TestClient(light=True)
+        test = textwrap.dedent("""
+            from conan import ConanFile
+            class TestBasicConanfile(ConanFile):
+                def requirements(self):
+                    self.requires(self.tested_reference_str)
+                    self.requires("pkga/1.0")
+                def test(self):
+                    pass
+            """)
+        c.save({"pkga/conanfile.py": GenConanfile("pkga", "1.0"),
+                "pkgb/conanfile.py": GenConanfile("pkgb", "1.0"),
+                "pkgb/test_package/conanfile.py": test})
+        c.run("create pkga")
+        c.run("lock create pkgb")
+
+        # alternative 1, relax lockfile
+        c.run("create pkgb --lockfile-partial")
+
+        # alternative 2, do not run test_package with same lockfile
+        c.run("create pkgb --test-folder=")
+        # the test_package can be tested later, so the lockfile-partial only affects the test_package
+        c.run("test pkgb/test_package pkgb/1.0 --lockfile=pkgb/conan.lock --lockfile-partial")
+        assert "Using lockfile:" in c.out
+
+        # alternative 3, create the lockfile in the test_package
+        c.run("lock create pkgb/test_package --lockfile=pkgb/conan.lock "
+              "--lockfile-out=pkgb/test_package/conan.lock")
+        lockfile = c.load("pkgb/test_package/conan.lock")
+        assert "pkga/1.0" in lockfile
+        c.run("test pkgb/test_package pkgb/1.0 --lockfile=pkgb/test_package/conan.lock "
+              "--lockfile-partial")
+
+        # alternative 4, add the test_package dependencies to the main lockfile
+        c.run("lock create pkgb/test_package --lockfile=pkgb/conan.lock "
+              "--lockfile-out=pkgb/conan.lock")
+        lockfile = c.load("pkgb/conan.lock")
+        assert "pkga/1.0" in lockfile
+        c.run("create pkgb --lockfile=pkgb/conan.lock")
+
 
 class TestErrorDuplicates:
     def test_error_duplicates(self):
diff --git a/test/integration/package_id/test_validate.py b/test/integration/package_id/test_validate.py
index bc9b89c93..71e778fb1 100644
--- a/test/integration/package_id/test_validate.py
+++ b/test/integration/package_id/test_validate.py
@@ -1,4 +1,6 @@
 import json
+import os
+import platform
 import re
 import textwrap
 import unittest
@@ -8,7 +10,7 @@ import pytest
 from conan.cli.exit_codes import ERROR_INVALID_CONFIGURATION, ERROR_GENERAL
 from conans.client.graph.graph import BINARY_INVALID
 from conan.test.assets.genconanfile import GenConanfile
-from conans.util.files import save
+from conans.util.files import save, load
 from conan.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID
 
 
@@ -698,6 +700,65 @@ class TestValidateCppstd:
         assert "compiler.cppstd=14" in client.out
         assert "compiler.cppstd=17" not in client.out
 
+    @pytest.mark.skipif(platform.system() == "Windows", reason="Needs at least 3 cppstd values available, msvc 191 does not")
+    def test_extension_properties_cppstd_compat_non_transitiveness(self):
+        """
+        The cppstd_compat is not transitive, so if a recipe has cppstd_compat=False,
+        its dependencies will still be checked for compatibility
+        """
+        tc = TestClient()
+        tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_setting("compiler"),
+                 "lib/conanfile.py": GenConanfile("lib", "1.0")
+                    .with_setting("compiler")
+                    .with_class_attribute('extension_properties = {"compatibility_cppstd": False}')
+                    .with_requirement("dep/1.0"),
+                 "app/conanfile.py": GenConanfile("app", "1.0")
+                    .with_setting("compiler")
+                    .with_requirement("lib/1.0")})
+        tc.run("create dep -s=compiler.cppstd=20")
+        tc.run("create lib -s=compiler.cppstd=17")
+        tc.run("create app -s=compiler.cppstd=14", assert_error=True)
+
+        tc.run("install --requires=app/1.0 -s=compiler.cppstd=11", assert_error=True)
+        assert "dep/1.0: Found compatible package" in tc.out
+        assert "ERROR: Missing binary: app/1.0" in tc.out
+
+    def test_extension_properties_make_transitive(self):
+        """
+        The cppstd_compat is not transitive, so if a recipe has cppstd_compat=False,
+        its dependencies will still be checked for compatibility
+        """
+        tc = TestClient()
+        tc.save({"lib/conanfile.py": GenConanfile("lib", "1.0").with_setting("compiler"),
+                 "dep/conanfile.py": GenConanfile("dep", "1.0")
+                    .with_setting("compiler")
+                    .with_class_attribute('extension_properties = {"compatibility_cppstd": False}')
+                    .with_requirement("lib/1.0"),
+                 "conanfile.py": GenConanfile("app", "1.0")
+                    .with_setting("compiler")
+                    .with_requirement("dep/1.0")})
+        tc.run("create lib -s=compiler.cppstd=17")
+        tc.run("create dep -s=compiler.cppstd=14")
+        tc.run("create . -s=compiler.cppstd=14")
+
+        compat_path = os.path.join(tc.cache_folder, "extensions/plugins/compatibility/compatibility.py")
+        compat_contents = load(compat_path)
+
+        transitive_expansions = textwrap.indent(textwrap.dedent("""
+        extension_properties = getattr(conanfile, "extension_properties", {}).copy()
+        for dep in conanfile.dependencies.values():
+            if not dep.extension_properties.get("compatibility_cppstd", True):
+                extension_properties["compatibility_cppstd"] = False
+        """), "    ")
+
+        compat_contents = compat_contents.replace('extension_properties = getattr(conanfile, "extension_properties", {})', transitive_expansions)
+        save(compat_path, compat_contents)
+
+        tc.run("install --requires=app/1.0 -s=compiler.cppstd=17", assert_error=True)
+
+        assert "Missing prebuilt package for 'app/1.0', 'dep/1.0'" in tc.out
+
+
 
 class TestCompatibleSettingsTarget(unittest.TestCase):
     """ aims to be a very close to real use case of tool being used across different settings_target
diff --git a/test/integration/remote/auth_bearer_test.py b/test/integration/remote/auth_bearer_test.py
index b5d05e531..19008a2c9 100644
--- a/test/integration/remote/auth_bearer_test.py
+++ b/test/integration/remote/auth_bearer_test.py
@@ -1,20 +1,10 @@
-import unittest
-
 from bottle import request
 
+from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient, TestServer
 
 
-conanfile = """
-from conan import ConanFile
-
-class OpenSSLConan(ConanFile):
-    name = "hello"
-    version = "0.1"
-"""
-
-
-class AuthorizationHeaderSpy(object):
+class AuthorizationHeaderSpy:
     """ Generic plugin to handle Authorization header. Must be extended and implement
     some abstract methods in subclasses"""
 
@@ -24,50 +14,32 @@ class AuthorizationHeaderSpy(object):
     def __init__(self):
         self.auths = []
 
-    def apply(self, callback, context):  # @UnusedVariable
+    def apply(self, callback, context):  # noqa
         auth = request.headers.get("Authorization")
         name = callback.__name__
         self.auths.append((name, auth))
         return callback
 
 
-class ReturnHandlerPlugin(object):
-
-    name = 'ReturnHandlerPluginSpy'
-    api = 2
-
-    def apply(self, callback, _):
-        """Apply plugin"""
-        def wrapper(*args, **kwargs):
-            """Capture possible exceptions to manage the return"""
-            result = callback(*args, **kwargs)
-            if isinstance(result, dict):
-                for k, v in result.items():
-                    result[k] = v.split("?signature=")[0]
-            return result
-        return wrapper
-
-
-class AuthorizeBearerTest(unittest.TestCase):
+class TestAuthorizeBearer:
 
     def test_basic(self):
         auth = AuthorizationHeaderSpy()
         server = TestServer(plugins=[auth])
-        servers = {"default": server}
-        client = TestClient(servers=servers, inputs=["admin", "password"])
-        client.save({"conanfile.py": conanfile})
+        client = TestClient(servers={"default": server}, inputs=["admin", "password"])
+        client.save({"conanfile.py": GenConanfile("hello", "0.1")})
         client.run("export . --user=lasote --channel=stable")
         errors = client.run("upload hello/0.1@lasote/stable -r default --only-recipe")
-        self.assertFalse(errors)
+        assert not errors
 
         expected_calls = [('get_recipe_revisions_references', None),
                           ('check_credentials', None),
                           ('authenticate', 'Basic'),
                           ('upload_recipe_file', 'Bearer')]
 
-        self.assertEqual(len(expected_calls), len(auth.auths))
+        assert len(expected_calls) == len(auth.auths)
         for i, (method, auth_type) in enumerate(expected_calls):
             real_call = auth.auths[i]
-            self.assertEqual(method, real_call[0])
+            assert method == real_call[0]
             if auth_type:
-                self.assertIn(auth_type, real_call[1])
+                assert auth_type in real_call[1]
diff --git a/test/unittests/client/conan_output_test.py b/test/unittests/client/conan_output_test.py
index fd35c68a4..ea35f7628 100644
--- a/test/unittests/client/conan_output_test.py
+++ b/test/unittests/client/conan_output_test.py
@@ -1,17 +1,17 @@
 import sys
-import unittest
 from unittest import mock
 
 import pytest
-from parameterized import parameterized
 
 from conan.api.output import ConanOutput, init_colorama
+from conan.test.utils.mocks import RedirectedTestOutput
+from conan.test.utils.tools import redirect_output
 
 
-class ConanOutputTest(unittest.TestCase):
+class TestConanOutput:
 
-    @parameterized.expand([(True, {"NO_COLOR": "1"}),
-                           (True, {"NO_COLOR": "0"})])
+    @pytest.mark.parametrize("isatty, env", [(True, {"NO_COLOR": "1"}),
+                                             (True, {"NO_COLOR": "0"})])
     def test_output_color_prevent_strip(self, isatty, env):
         with mock.patch("colorama.init") as init:
             with mock.patch("sys.stderr.isatty", return_value=isatty), \
@@ -38,10 +38,13 @@ def test_output_forced(force):
 
 
 def test_output_chainable():
-    try:
+    stderr = RedirectedTestOutput()
+    with redirect_output(stderr):
         ConanOutput(scope="My package")\
             .title("My title")\
             .highlight("Worked")\
             .info("But there was more that needed to be said")
-    except Exception as e:
-        assert False, f"Chained ConanOutput write methods raised an exception {e}"
+    assert "My package" in stderr.getvalue()
+    assert "My title" in stderr.getvalue()
+    assert "Worked" in stderr.getvalue()
+    assert "But there was more that needed to be said" in stderr.getvalue()
diff --git a/test/unittests/client/rest/downloader_test.py b/test/unittests/client/rest/downloader_test.py
index b63415358..efdf530ac 100644
--- a/test/unittests/client/rest/downloader_test.py
+++ b/test/unittests/client/rest/downloader_test.py
@@ -9,7 +9,7 @@ from conans.client.downloaders.file_downloader import FileDownloader
 from conan.errors import ConanException
 
 
-class MockResponse(object):
+class MockResponse:
     def __init__(self, data, headers, status_code=200):
         self.data = data
         self.ok = True
@@ -25,9 +25,7 @@ class MockResponse(object):
         pass
 
 
-class MockRequester(object):
-    retry = 0
-    retry_wait = 0
+class MockRequester:
 
     def __init__(self, data, chunk_size=None, accept_ranges=True, echo_header=None):
         self._data = data
@@ -44,15 +42,11 @@ class MockRequester(object):
         headers = {"Content-Length": len(self._data), "Accept-Ranges": "bytes"}
         if match and self._accept_ranges:
             start = int(match.groups()[0])
-            if start < len(self._data):
-                status = 206
-                headers.update({"Content-Length": str(len(self._data) - start),
-                                "Content-Range": "bytes {}-{}/{}".format(start, len(self._data) - 1,
-                                                                         len(self._data))})
-            else:
-                status = 416
-                headers.update({"Content-Length": "0",
-                                "Content-Range": "bytes */{}".format(len(self._data))})
+            assert start < len(self._data)
+            status = 206
+            headers.update({"Content-Length": str(len(self._data) - start),
+                            "Content-Range": "bytes {}-{}/{}".format(start, len(self._data) - 1,
+                                                                     len(self._data))})
         else:
             headers.update(self._echo_header)
         response = MockResponse(self._data[start:start + self._chunk_size], status_code=status,
diff --git a/test/unittests/client/rest/uploader_test.py b/test/unittests/client/rest/uploader_test.py
index e8a6fbb71..5889cbf4f 100644
--- a/test/unittests/client/rest/uploader_test.py
+++ b/test/unittests/client/rest/uploader_test.py
@@ -8,14 +8,11 @@ from conans.util.files import save
 
 
 class _ConfigMock:
-    def __getitem__(self, item):
-        return None
-
     def get(self, name, default=None, check_type=None):
         return default
 
 
-class MockRequester(object):
+class MockRequester:
     retry = 0
     retry_wait = 0
 
diff --git a/test/unittests/model/build_info/components_test.py b/test/unittests/model/build_info/components_test.py
index ff4c2259b..28ed5bc55 100644
--- a/test/unittests/model/build_info/components_test.py
+++ b/test/unittests/model/build_info/components_test.py
@@ -15,6 +15,9 @@ class CppInfoComponentsTest(unittest.TestCase):
         self.assertListEqual(cpp_info.components["libb"].includedirs, ["include", "includewhat"])
         self.assertListEqual(cpp_info.components["libc"].libs, ["thelibc"])
 
+        with self.assertRaises(AttributeError):
+            cpp_info.required_components = ["liba::liba"]
+
     def test_no_components_inside_components(self):
         cpp_info = CppInfo()
         with self.assertRaises(AttributeError):
diff --git a/test/unittests/server/service/service_test.py b/test/unittests/server/service/service_test.py
index bdb8f9dc1..96f8f5655 100644
--- a/test/unittests/server/service/service_test.py
+++ b/test/unittests/server/service/service_test.py
@@ -17,16 +17,6 @@ from conan.test.utils.test_files import temp_folder
 from conans.util.files import save, save_files
 
 
-class MockFileSaver(object):
-
-    def __init__(self, filename, content):
-        self.filename = filename
-        self.content = content
-
-    def save(self, abspath):
-        save(os.path.join(abspath, self.filename), self.content)
-
-
 DEFAULT_REVISION = "1234"
 
 
diff --git a/test/unittests/tools/files/test_downloads.py b/test/unittests/tools/files/test_downloads.py
index 165001b46..25019d08a 100644
--- a/test/unittests/tools/files/test_downloads.py
+++ b/test/unittests/tools/files/test_downloads.py
@@ -1,5 +1,6 @@
 import os
 import shutil
+from unittest import mock
 
 import pytest
 
@@ -13,27 +14,31 @@ from conan.test.utils.tools import redirect_output, TestRequester
 from conans.util.files import save, load, chdir, mkdir
 
 
-@pytest.mark.skip(reason="This causes more troubles than benefits, external ftp download is testing "
-                         "very little conan code, mostly python")
+@mock.patch('ftplib.FTP', autospec=True)
 class TestFTP:
 
-    def test_ftp_auth(self):
-        filename = "/pub/example/readme.txt"
-        conanfile = ConanFileMock()
-        ftp_download(conanfile, "test.rebex.net", filename, "demo", "password")
-        assert os.path.exists(os.path.basename(filename))
-
-    def test_ftp_invalid_path(self):
+    def test_ftp_auth(self, ftp_mock):
+        ftp_object = ftp_mock.return_value
+        filename = "/pub/example/readme2.txt"
+        with chdir(temp_folder()):  # To not pollute the project
+            ftp_download(None, "test.rebex.net", filename, "demo", "password")
+        ftp_mock.assert_called_with('test.rebex.net')
+        assert ftp_object.login.called
+        ftp_object.cwd.assert_called_with('/pub/example')
+
+    def test_ftp_invalid_path(self, ftp_mock):
+        ftp_object = ftp_mock.return_value
+        ftp_object.cwd.side_effect = Exception("550 The system cannot find the file specified.")
         with pytest.raises(ConanException) as exc:
-            conanfile = ConanFileMock()
-            ftp_download(conanfile, "test.rebex.net", "invalid-file", "demo", "password")
+            ftp_download(None, "test.rebex.net", "/path/invalid-file", "demo", "password")
         assert "550 The system cannot find the file specified." in str(exc.value)
         assert not os.path.exists("invalid-file")
 
-    def test_ftp_invalid_auth(self):
+    def test_ftp_invalid_auth(self, ftp_mock):
+        ftp_object = ftp_mock.return_value
+        ftp_object.login.side_effect = Exception("530 User cannot log in.")
         with pytest.raises(ConanException) as exc:
-            conanfile = ConanFileMock()
-            ftp_download(conanfile, "test.rebex.net", "readme.txt", "demo", "invalid")
+            ftp_download(None, "test.rebex.net", "readme.txt", "demo", "invalid")
         assert "530 User cannot log in." in str(exc.value)
         assert not os.path.exists("readme.txt")
 
diff --git a/test/unittests/tools/files/test_zipping.py b/test/unittests/tools/files/test_zipping.py
index 0c41a494d..655271ad5 100644
--- a/test/unittests/tools/files/test_zipping.py
+++ b/test/unittests/tools/files/test_zipping.py
@@ -137,7 +137,7 @@ def test_untargz_with_strip_root_fails():
     dest_dir = temp_folder()
     with pytest.raises(ConanException) as error:
         unzip(conanfile, archive, dest_dir, strip_root=True)
-    assert "The tgz file contains more than 1 folder in the root" in str(error.value)
+    assert "Can't untar a tgz containing files in the root with strip_root enabled" in str(error.value)
 
 
 def test_untargz_with_strip_root_and_pattern():
diff --git a/test/unittests/tools/gnu/autotoolschain_test.py b/test/unittests/tools/gnu/autotoolschain_test.py
index eec8d7cce..43d79fc43 100644
--- a/test/unittests/tools/gnu/autotoolschain_test.py
+++ b/test/unittests/tools/gnu/autotoolschain_test.py
@@ -126,9 +126,8 @@ def test_get_gnu_triplet_for_cross_building_raise_error():
     conanfile.settings_build = MockSettings({"os": "Solaris", "arch": "x86"})
     with pytest.raises(ConanException) as conan_error:
         AutotoolsToolchain(conanfile)
-        msg = "'compiler' parameter for 'get_gnu_triplet()' is not specified and " \
-              "needed for os=Windows"
-        assert msg == str(conan_error.value)
+    msg = "'compiler' parameter for 'get_gnu_triplet()' is not specified and needed for os=Windows"
+    assert msg == str(conan_error.value)
 
 
 def test_compilers_mapping():
diff --git a/test/unittests/tools/gnu/test_gnutoolchain.py b/test/unittests/tools/gnu/test_gnutoolchain.py
index 48fffbe94..b18e2119f 100644
--- a/test/unittests/tools/gnu/test_gnutoolchain.py
+++ b/test/unittests/tools/gnu/test_gnutoolchain.py
@@ -124,9 +124,8 @@ def test_get_gnu_triplet_for_cross_building_raise_error():
     conanfile.settings_build = MockSettings({"os": "Solaris", "arch": "x86"})
     with pytest.raises(ConanException) as conan_error:
         GnuToolchain(conanfile)
-        msg = "'compiler' parameter for 'get_gnu_triplet()' is not specified and " \
-              "needed for os=Windows"
-        assert msg == str(conan_error.value)
+    msg = "'compiler' parameter for 'get_gnu_triplet()' is not specified and needed for os=Windows"
+    assert msg == str(conan_error.value)
 
 
 def test_compilers_mapping():
diff --git a/test/unittests/tools/google/test_bazel.py b/test/unittests/tools/google/test_bazel.py
index b6250f6ee..ec9dfcc05 100644
--- a/test/unittests/tools/google/test_bazel.py
+++ b/test/unittests/tools/google/test_bazel.py
@@ -1,41 +1,12 @@
-import os
 import platform
-from unittest.mock import MagicMock
 
 import pytest
 
 from conan.test.utils.mocks import ConanFileMock
-from conan.test.utils.test_files import temp_folder
-from conan.tools.files import save
 from conan.tools.google import Bazel
 from conan.tools.google.bazeldeps import _relativize_path
 
 
-@pytest.fixture(scope="module")
-def cpp_info():
-    folder = temp_folder(path_with_spaces=False)
-    bindirs = os.path.join(folder, "bin")
-    libdirs = os.path.join(folder, "lib")
-    save(ConanFileMock(), os.path.join(bindirs, "mylibwin.dll"), "")
-    save(ConanFileMock(), os.path.join(bindirs, "mylibwin2.dll"), "")
-    save(ConanFileMock(), os.path.join(bindirs, "myliblin.so"), "")
-    save(ConanFileMock(), os.path.join(bindirs, "mylibmac.dylib"), "")
-    save(ConanFileMock(), os.path.join(bindirs, "protoc"), "")  # binary
-    save(ConanFileMock(), os.path.join(libdirs, "myliblin.a"), "")
-    save(ConanFileMock(), os.path.join(libdirs, "mylibmac.a"), "")
-    save(ConanFileMock(), os.path.join(libdirs, "mylibwin.lib"), "")
-    save(ConanFileMock(), os.path.join(libdirs, "mylibwin2.if.lib"), "")
-    save(ConanFileMock(), os.path.join(libdirs, "libmylib.so"), "")
-    save(ConanFileMock(), os.path.join(libdirs, "subfolder", "libmylib.a"), "")  # recursive
-    cpp_info_mock = MagicMock(_base_folder=None, libdirs=[], bindirs=[], libs=[],
-                              aggregated_components=MagicMock())
-    cpp_info_mock._base_folder = folder.replace("\\", "/")
-    cpp_info_mock.libdirs = [libdirs]
-    cpp_info_mock.bindirs = [bindirs]
-    cpp_info_mock.aggregated_components.return_value = cpp_info_mock
-    return cpp_info_mock
-
-
 @pytest.mark.skipif(platform.system() == "Windows", reason="Remove this skip for Conan 2.x"
                                                            "Needs conanfile.commands")
 def test_bazel_command_with_empty_config():
diff --git a/test/unittests/util/files/strip_root_extract_test.py b/test/unittests/util/files/strip_root_extract_test.py
index 3f8746b99..d65bcb367 100644
--- a/test/unittests/util/files/strip_root_extract_test.py
+++ b/test/unittests/util/files/strip_root_extract_test.py
@@ -255,5 +255,20 @@ class TarExtractPlainTest(unittest.TestCase):
 
         # Extract without the subfolder
         extract_folder = temp_folder()
-        with self.assertRaisesRegex(ConanException, "The tgz file contains a file in the root"):
+        with self.assertRaisesRegex(ConanException, "Can't untar a tgz containing files in the root with strip_root enabled"):
+            unzip(ConanFileMock(), tgz_file, destination=extract_folder, strip_root=True)
+
+    def test_invalid_flat_multiple_file(self):
+        tmp_folder = temp_folder()
+        with chdir(tmp_folder):
+            save("file1", "contentsfile1")
+            save("file2", "contentsfile2")
+
+        zip_folder = temp_folder()
+        tgz_file = os.path.join(zip_folder, "file.tar.gz")
+        self._compress_folder(tmp_folder, tgz_file)
+
+        # Extract without the subfolder
+        extract_folder = temp_folder()
+        with self.assertRaisesRegex(ConanException, "Can't untar a tgz containing files in the root with strip_root enabled"):
             unzip(ConanFileMock(), tgz_file, destination=extract_folder, strip_root=True)
diff --git a/test/unittests/util/tools_test.py b/test/unittests/util/tools_test.py
index a7c14476a..b60a008df 100644
--- a/test/unittests/util/tools_test.py
+++ b/test/unittests/util/tools_test.py
@@ -16,13 +16,7 @@ from conans.util.files import load, md5, save
 from conans.util.runners import check_output_runner
 
 
-class ConfigMock:
-    def __init__(self):
-        self.retry = 0
-        self.retry_wait = 0
-
-
-class RunnerMock(object):
+class RunnerMock:
     def __init__(self, return_ok=True, output=None):
         self.command_called = None
         self.return_ok = return_ok
@@ -81,8 +75,6 @@ class ToolsTest(unittest.TestCase):
         self.assertEqual(os.getenv("Z", None), None)
 
     def test_check_output_runner(self):
-        original_temp = temp_folder()
-        patched_temp = os.path.join(original_temp, "dir with spaces")
         payload = "hello world"
         output = check_output_runner("echo {}".format(payload), stderr=subprocess.STDOUT)
         self.assertIn(payload, str(output))