Repository: conan-io/conan
Pull request #17643 (2025-01-27 13:16:10)

test build info with equivalent packages


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 000000000..02a8ad199
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,103 @@
+name: Main Workflow
+on:
+  push:
+    branches:
+      - develop2
+      - release/*
+      - '*'
+  pull_request:
+    branches:
+      - '*'
+      - 'release/*'
+  workflow_dispatch:
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  linux_suite:
+    uses: ./.github/workflows/linux-tests.yml
+    name: Linux test suite
+
+  osx_suite:
+    uses: ./.github/workflows/osx-tests.yml
+    name: OSX test suite
+
+  windows_suite:
+    uses: ./.github/workflows/win-tests.yml
+    name: Windows test suite
+
+  code_coverage:
+    runs-on: ubuntu-latest
+    name: Code coverage
+    if: github.ref == 'refs/heads/develop2'         # Only measure code coverage on main branch
+    needs: [linux_suite, osx_suite, windows_suite]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Download coverage artifacts
+        uses: actions/download-artifact@v4
+        with:
+          merge-multiple: true
+
+      - name: Merge coverage reports
+        run: |
+          pip install coverage
+          coverage combine
+          coverage report
+          coverage xml
+
+      - name: Code coverage
+        uses: codecov/codecov-action@v5
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+
+      - uses: geekyeggo/delete-artifact@v5
+        with:
+          name: |
+            .coverage.*
+
+  deploy_to_pypi_test:
+    runs-on: ubuntu-latest
+    name: Deploy to TestPyPI
+    if: github.ref == 'refs/heads/develop2'
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.9
+
+      - name: Install dependencies
+        run: |
+          pip install --upgrade pip
+          pip install twine
+
+      - name: Bump Dev Version
+        run: |
+          python .ci/bump_dev_version.py
+
+      - name: Build Package
+        run: |
+          python setup.py sdist
+
+      - name: Upload to TestPyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.TEST_PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }}
+        run: |
+          python -m twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
+
+      - name: Deploy conan-server to TestPyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.TEST_PYPI_SERVER_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.TEST_PYPI_SERVER_PASSWORD }}
+        run: |
+          rm -rf dist/
+          mv setup_server.py setup.py
+          python setup.py sdist
+          python -m twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
diff --git a/.gitignore b/.gitignore
index 87824d101..593c94534 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ parts/
 sdist/
 var/
 venv/
+.venv/
 *.egg-info/
 .installed.cfg
 *.egg
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 000000000..561bf760e
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,1 @@
+comment: false # Disable codecov PR comments -> leave only the checks
diff --git a/conan/internal/model/version.py b/conan/internal/model/version.py
index dd87b3586..ab7518c3f 100644
--- a/conan/internal/model/version.py
+++ b/conan/internal/model/version.py
@@ -1,4 +1,5 @@
 from functools import total_ordering
+from typing import Optional
 
 from conan.errors import ConanException
 
@@ -193,3 +194,8 @@ class Version:
                     return self._nonzero_items < other._nonzero_items
             else:  # None of them is pre-release
                 return (self._nonzero_items, self._build) < (other._nonzero_items, other._build)
+
+    def in_range(self, version_range: str, resolve_prerelease: Optional[bool] = None):
+        """ Check if the version is in the specified range """
+        from conan.internal.model.version_range import VersionRange
+        return VersionRange(version_range).contains(self, resolve_prerelease=resolve_prerelease)
diff --git a/conan/tools/cmake/cmakedeps2/cmakedeps.py b/conan/tools/cmake/cmakedeps2/cmakedeps.py
index a68a9b53e..e190c268a 100644
--- a/conan/tools/cmake/cmakedeps2/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps2/cmakedeps.py
@@ -165,30 +165,55 @@ class _PathGenerator:
         self._conanfile = conanfile
         self._cmakedeps = cmakedeps
 
+    def _get_cmake_paths(self, requirements, dirs_name):
+        paths = {}
+        cmake_vars = {
+            "bindirs": "CMAKE_PROGRAM_PATH",
+            "libdirs": "CMAKE_LIBRARY_PATH",
+            "includedirs": "CMAKE_INCLUDE_PATH",
+        }
+        for req, dep in requirements:
+            cppinfo = dep.cpp_info.aggregated_components()
+            cppinfo_dirs = getattr(cppinfo, dirs_name, [])
+            if not cppinfo_dirs:
+                continue
+            previous = paths.get(req.ref.name)
+            if previous:
+                self._conanfile.output.info(f"There is already a '{req.ref}' package contributing"
+                                            f" to {cmake_vars[dirs_name]}. Using the one"
+                                            f" defined by the context={dep.context}.")
+            paths[req.ref.name] = cppinfo_dirs
+        return [d for dirs in paths.values() for d in dirs]
+
     def generate(self):
         template = textwrap.dedent("""\
-            {% for pkg_name, folder in pkg_paths.items() %}
-            set({{pkg_name}}_DIR "{{folder}}")
-            {% endfor %}
-            {% if host_runtime_dirs %}
-            set(CONAN_RUNTIME_LIB_DIRS {{ host_runtime_dirs }} )
-            # Only for VS, needs CMake>=3.27
-            set(CMAKE_VS_DEBUGGER_ENVIRONMENT "PATH=${CONAN_RUNTIME_LIB_DIRS};%PATH%")
-            {% endif %}
-            {% if cmake_program_path %}
-            list(PREPEND CMAKE_PROGRAM_PATH {{ cmake_program_path }})
-            {% endif %}
-            """)
-
+        {% for pkg_name, folder in pkg_paths.items() %}
+        set({{pkg_name}}_DIR "{{folder}}")
+        {% endfor %}
+        {% if host_runtime_dirs %}
+        set(CONAN_RUNTIME_LIB_DIRS {{ host_runtime_dirs }} )
+        # Only for VS, needs CMake>=3.27
+        set(CMAKE_VS_DEBUGGER_ENVIRONMENT "PATH=${CONAN_RUNTIME_LIB_DIRS};%PATH%")
+        {% endif %}
+        {% if cmake_program_path %}
+        list(PREPEND CMAKE_PROGRAM_PATH {{ cmake_program_path }})
+        {% endif %}
+        {% if cmake_library_path %}
+        list(PREPEND CMAKE_LIBRARY_PATH {{ cmake_library_path }})
+        {% endif %}
+        {% if cmake_include_path %}
+        list(PREPEND CMAKE_INCLUDE_PATH {{ cmake_include_path }})
+        {% endif %}
+        """)
         host_req = self._conanfile.dependencies.host
         build_req = self._conanfile.dependencies.direct_build
         test_req = self._conanfile.dependencies.test
-
+        all_reqs = list(host_req.items()) + list(test_req.items()) + list(build_req.items())
         # gen_folder = self._conanfile.generators_folder.replace("\\", "/")
         # if not, test_cmake_add_subdirectory test fails
         # content.append('set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)')
         pkg_paths = {}
-        for req, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
+        for req, dep in all_reqs:
             cmake_find_mode = self._cmakedeps.get_property("cmake_find_mode", dep)
             cmake_find_mode = cmake_find_mode or FIND_MODE_CONFIG
             cmake_find_mode = cmake_find_mode.lower()
@@ -214,26 +239,17 @@ class _PathGenerator:
             # content.append(f'set({pkg_name}_ROOT "{gen_folder}")')
             pkg_paths[pkg_name] = "${CMAKE_CURRENT_LIST_DIR}"
 
-        # CMAKE_PROGRAM_PATH
-        cmake_program_path = {}
-        for req, dep in list(host_req.items()) + list(test_req.items()) + list(build_req.items()):
-            if not req.direct:
-                continue
-            cppinfo = dep.cpp_info.aggregated_components()
-            if not cppinfo.bindirs:
-                continue
-            previous = cmake_program_path.get(req.ref.name)
-            if previous:
-                self._conanfile.output.info(f"There is already a '{req.ref}' package "
-                                            f"contributing to CMAKE_PROGRAM_PATH. The one with "
-                                            f"build={req.build} test={req.test} will be used")
-
-            cmake_program_path[req.ref.name] = cppinfo.bindirs
-        cmake_program_path = [d for dirs in cmake_program_path.values() for d in dirs]
+        # CMAKE_PROGRAM_PATH | CMAKE_LIBRARY_PATH | CMAKE_INCLUDE_PATH
+        cmake_program_path = self._get_cmake_paths([(req, dep) for req, dep in all_reqs if req.direct], "bindirs")
+        cmake_library_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "libdirs")
+        cmake_include_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "includedirs")
 
         context = {"host_runtime_dirs": self._get_host_runtime_dirs(),
                    "pkg_paths": pkg_paths,
-                   "cmake_program_path": _join_paths(self._conanfile, cmake_program_path)}
+                   "cmake_program_path": _join_paths(self._conanfile, cmake_program_path),
+                   "cmake_library_path": _join_paths(self._conanfile, cmake_library_path),
+                   "cmake_include_path": _join_paths(self._conanfile, cmake_include_path),
+        }
         content = Template(template, trim_blocks=True, lstrip_blocks=True).render(context)
         save(self._conanfile, self._conan_cmakedeps_paths, content)
 
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
index 61155fdce..c42d9f060 100644
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -438,7 +438,8 @@ class EnvVars:
         content = "\n".join(result)
         # It is very important to save it correctly with utf-8, the Conan util save() is broken
         os.makedirs(os.path.dirname(os.path.abspath(file_location)), exist_ok=True)
-        open(file_location, "w", encoding="utf-8").write(content)
+        with open(file_location, "w", encoding="utf-8") as f:
+            f.write(content)
 
     def save_ps1(self, file_location, generate_deactivate=True,):
         _, filename = os.path.split(file_location)
diff --git a/conans/requirements_dev.txt b/conans/requirements_dev.txt
index e9e964c45..b2876e233 100644
--- a/conans/requirements_dev.txt
+++ b/conans/requirements_dev.txt
@@ -8,3 +8,4 @@ PyJWT
 pluginbase
 docker
 setuptools
+pytest-cov
diff --git a/setup.cfg b/setup.cfg
index 6ba9f60b4..ca70e3f2f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,11 @@ count = False
 max-line-length = 100
 statistics = False
 
+; Coverage files are generated across different OS and machines, thus, coverage
+; reports will contain different absolute paths to reference to the same
+; project relative paths.
+; This option makes coverage to use relative paths to the project root,
+; removing the need for setting arbitrary runners paths described in here:
+; https://coverage.readthedocs.io/en/coverage-4.0.3/config.html#paths
+[coverage:run]
+relative_files = True


Test patch:
diff --git a/.github/actions/test-coverage/action.yml b/.github/actions/test-coverage/action.yml
new file mode 100644
index 000000000..666b10d11
--- /dev/null
+++ b/.github/actions/test-coverage/action.yml
@@ -0,0 +1,46 @@
+
+description: 'Run tests enabling coverage in certain conditions and upload coverage artifacts for later process'
+
+inputs:
+  python-version:
+    description: 'Python version in which the tests was ran'
+    required: true
+
+  test-type:
+    description: 'Test suite name'
+    required: true
+
+  duration:
+    description: 'Tests maximum duration'
+    required: true
+    default: '10'
+
+  tests:
+    description: 'Tests folder and options to run'
+    required: false
+
+  workers:
+    description: 'Number of workers to run tests'
+    default: auto
+
+runs:
+  using: 'composite'
+  steps:
+      - name: Run tests with coverage
+        if: ${{ inputs.tests }}
+        shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
+        run: |
+          pytest ${{ inputs.tests }} --durations=${{ inputs.duration }} -n ${{ inputs.workers }} ${{ github.ref == 'refs/heads/develop2' && '--cov=conan --cov=conans --cov=test --cov-report=term-missing:skip-covered' || '' }}
+
+      - name: Rename coverage file
+        if: github.ref == 'refs/heads/develop2'
+        shell: bash
+        run: mv .coverage .coverage.${{ runner.os }}-${{ inputs.python-version }}-${{ inputs.test-type }}
+
+      - name: Upload coverage artifact
+        if: github.ref == 'refs/heads/develop2'
+        uses: actions/upload-artifact@v4
+        with:
+          name: .coverage.${{ runner.os }}-${{ inputs.python-version }}-${{ inputs.test-type }}
+          path: .coverage.${{ runner.os }}-${{ inputs.python-version }}-${{ inputs.test-type }}
+          include-hidden-files: true
diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml
index 558039da0..1df2d43d1 100644
--- a/.github/workflows/linux-tests.yml
+++ b/.github/workflows/linux-tests.yml
@@ -1,23 +1,12 @@
 name: Linux tests
 
 on:
-  push:
-    branches:
-      - develop2
-      - release/*
-  pull_request:
-    branches:
-      - '*'
-      - 'release/*'
-  workflow_dispatch:
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
-  cancel-in-progress: true
+  workflow_call:
 
 jobs:
   build_container:
     runs-on: ubuntu-latest
+    name: Build Linux container
     outputs:
       image_tag: ${{ steps.dockerfile_hash.outputs.tag }}
     steps:
@@ -52,7 +41,7 @@ jobs:
           docker build -t ghcr.io/${{ github.repository_owner }}/conan-tests:${{ steps.dockerfile_hash.outputs.tag }} -f ./.ci/docker/conan-tests .
           docker push ghcr.io/${{ github.repository_owner }}/conan-tests:${{ steps.dockerfile_hash.outputs.tag }}
 
-  linux_test_suite:
+  linux_tests:
     needs: build_container
     runs-on: ubuntu-latest
     container:
@@ -60,9 +49,16 @@ jobs:
       options: --user conan
     strategy:
       matrix:
-        python-version: ['3.12.3', '3.9.2', '3.8.6', '3.6.15']
-        test-type: ['unittests', 'integration', 'functional']
-    name: Conan ${{ matrix.test-type }} (${{ matrix.python-version }})
+        python-version: [3.12.3, 3.9.2, 3.8.6, 3.6.15]
+        test-type: [unittests, integration, functional]
+        include:
+        - test-type: unittests
+          test-name: Unit
+        - test-type: integration
+          test-name: Integration
+        - test-type: functional
+          test-name: Functional
+    name: ${{ matrix.test-name }} Tests (${{ matrix.python-version }})
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
@@ -87,23 +83,20 @@ jobs:
           pip install meson
 
       - name: Run tests
-        shell: bash
-        run: |
-          if [ "${{ matrix.test-type }}" == "unittests" ]; then
-            pytest test/unittests --durations=20 -n 4
-          elif [ "${{ matrix.test-type }}" == "integration" ]; then
-            pytest test/integration --durations=20 -n 4
-          elif [ "${{ matrix.test-type }}" == "functional" ]; then
-            pytest test/functional --durations=20 -n 4
-          fi
+        uses: ./.github/actions/test-coverage
+        with:
+          python-version: ${{ matrix.python-version }}
+          test-type: ${{ matrix.test-type }}
+          tests: test/${{ matrix.test-type }}
+          duration: 20
 
   linux_docker_tests:
     needs: build_container
     runs-on: ubuntu-latest
-    name: Docker Runner Tests
     strategy:
       matrix:
         python-version: [3.12, 3.9]
+    name: Docker Runner Tests (${{ matrix.python-version }})
     steps:
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v5
@@ -122,50 +115,10 @@ jobs:
           pip install -r conans/requirements_runner.txt
 
       - name: Run tests
-        shell: bash
-        run: |
-          pytest -m docker_runner --durations=20 -rs
-
-  deploy_to_pypi_test:
-    needs: [build_container, linux_test_suite, linux_docker_tests]
-    runs-on: ubuntu-latest
-    if: github.ref == 'refs/heads/develop2'
-    name: Deploy to TestPyPI
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-
-      - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: ./.github/actions/test-coverage
         with:
-          python-version: '3.9'
-
-      - name: Install dependencies
-        run: |
-          pip install --upgrade pip
-          pip install twine
-
-      - name: Bump Dev Version
-        run: |
-          python .ci/bump_dev_version.py
-
-      - name: Build Package
-        run: |
-          python setup.py sdist
-
-      - name: Upload to TestPyPI
-        env:
-          TWINE_USERNAME: ${{ secrets.TEST_PYPI_USERNAME }}
-          TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }}
-        run: |
-          python -m twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
-
-      - name: Deploy conan-server to TestPyPI
-        env:
-          TWINE_USERNAME: ${{ secrets.TEST_PYPI_SERVER_USERNAME }}
-          TWINE_PASSWORD: ${{ secrets.TEST_PYPI_SERVER_PASSWORD }}
-        run: |
-          rm -rf dist/
-          mv setup_server.py setup.py
-          python setup.py sdist
-          python -m twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
+          python-version: ${{ matrix.python-version }}
+          test-type: docker
+          tests: '-m docker_runner -rs'
+          duration: 20
+          workers: 1
diff --git a/.github/workflows/osx-tests.yml b/.github/workflows/osx-tests.yml
index 671f8d6bf..065afbc33 100644
--- a/.github/workflows/osx-tests.yml
+++ b/.github/workflows/osx-tests.yml
@@ -1,21 +1,10 @@
 name: OSX Tests
 
 on:
-  push:
-    branches:
-      - develop2
-      - release/*
-  pull_request:
-    branches:
-      - '*'
-      - 'release/*'
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
-  cancel-in-progress: true
+  workflow_call:
 
 jobs:
-  setup-caches:
+  osx_setup:
     runs-on: macos-14
     name: Setup and Cache Dependencies
     steps:
@@ -118,18 +107,22 @@ jobs:
             chmod +x ${HOME}/Applications/bazel/${version}/bazel
           done
 
-  testing:
-    needs: setup-caches
+  osx_tests:
+    needs: osx_setup
+    runs-on: macos-14
     strategy:
       fail-fast: true
       matrix:
-        python-version: ['3.8', '3.12', '3.13']
+        python-version: [3.8, 3.12, 3.13]
         test-type: [unittests, integration, functional]
-
-    runs-on: macos-14
-
-    name: Conan (${{ matrix.test-type }}) (${{ matrix.python-version }})
-
+        include:
+        - test-type: unittests
+          test-name: Unit
+        - test-type: integration
+          test-name: Integration
+        - test-type: functional
+          test-name: Functional
+    name: ${{ matrix.test-name }} Tests (${{ matrix.python-version }})
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
@@ -168,11 +161,14 @@ jobs:
       - name: Install homebrew dependencies
         run: |
           brew install xcodegen make libtool zlib autoconf automake ninja
-
-      - name: Run Tests
-        run: |
           export PATH=${HOME}/Applications/CMake/3.15.7/bin:$PATH:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
           cmake --version
           bazel --version
 
-          python -m pytest test/${{ matrix.test-type }} --durations=20 -n auto
+      - name: Run tests
+        uses: ./.github/actions/test-coverage
+        with:
+          python-version: ${{ matrix.python-version }}
+          test-type: ${{ matrix.test-type }}
+          tests: test/${{ matrix.test-type }}
+          duration: 20
diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml
index 2b0df32c8..35214637a 100644
--- a/.github/workflows/win-tests.yml
+++ b/.github/workflows/win-tests.yml
@@ -1,27 +1,14 @@
 name: Windows Tests
 
 on:
-  push:
-    branches:
-      - develop2
-      - release/*
-  pull_request:
-    branches:
-      - '*'
-      - 'release/*'
-  workflow_dispatch:
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
-  cancel-in-progress: true
+  workflow_call:
 
 jobs:
   unit_integration_tests:
     runs-on: windows-2022
     strategy:
       matrix:
-        python-version: ['3.13', '3.8', '3.6']
-
+        python-version: [3.13, 3.8, 3.6]
     name: Unit & Integration Tests (${{ matrix.python-version }})
     steps:
       - name: Checkout code
@@ -45,7 +32,6 @@ jobs:
         shell: pwsh
         run: echo "PIP_CACHE_DIR=$(pip cache dir)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
 
-
       - name: Cache pip packages
         uses: actions/cache@v4
         with:
@@ -60,18 +46,21 @@ jobs:
           pip install -r conans/requirements.txt
           pip install -r conans/requirements_dev.txt
           pip install -r conans/requirements_server.txt
+          git config --global core.autocrlf false
 
       - name: Run Unit & Integration Tests
-        shell: pwsh
-        run: |
-          git config --global core.autocrlf false
-          pytest test/unittests test/integration --durations=100 -n=auto
+        uses: ./.github/actions/test-coverage
+        with:
+          python-version: ${{ matrix.python-version }}
+          test-type: unit-integration
+          tests: test/unittests test/integration
+          duration: 100
 
   functional_tests:
     runs-on: windows-2022
     strategy:
       matrix:
-        python-version: ['3.13', '3.8', '3.6']
+        python-version: [3.13, 3.8, 3.6]
 
     name: Functional Tests (${{ matrix.python-version }})
     steps:
@@ -227,7 +216,7 @@ jobs:
               Remove-Item $zipFile
           }
 
-      - name: Run Functional Tests
+      - name: Prepare environment for functional tests
         run: |
           git config --global core.autocrlf false
 
@@ -260,4 +249,15 @@ jobs:
           [System.Environment]::SetEnvironmentVariable('MSYS2_PATH', $msys2Path, [System.EnvironmentVariableTarget]::Process)
           Write-Host "Added MSYS2_PATH environment variable: $msys2Path"
 
-          pytest test/functional -n=auto --durations=100
+          # Export variables so they persist in the next steps
+          echo "PATH=$env:Path" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+          echo "CONAN_TEST_FOLDER=$env:CONAN_TEST_FOLDER" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+          echo "MSYS2_PATH=$msys2Path" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+      - name: Run Functional Tests
+        uses: ./.github/actions/test-coverage
+        with:
+          python-version: ${{ matrix.python-version }}
+          test-type: functional
+          tests: test/functional
+          duration: 100
diff --git a/conan/test/utils/file_server.py b/conan/test/utils/file_server.py
index 183c5c537..005c41c2a 100644
--- a/conan/test/utils/file_server.py
+++ b/conan/test/utils/file_server.py
@@ -9,6 +9,8 @@ from conans.util.files import mkdir
 
 
 class TestFileServer:
+    __test__ = False
+
     def __init__(self, store=None):
         self.store = store or temp_folder(path_with_spaces=False)
         mkdir(self.store)
diff --git a/conan/test/utils/tools.py b/conan/test/utils/tools.py
index c7c1a63e4..b143a7502 100644
--- a/conan/test/utils/tools.py
+++ b/conan/test/utils/tools.py
@@ -288,7 +288,9 @@ class TestRequester:
         return requests.codes
 
 
-class TestServer(object):
+class TestServer:
+    __test__ = False
+
     def __init__(self, read_permissions=None,
                  write_permissions=None, users=None, plugins=None, base_path=None,
                  server_capabilities=None, complete_urls=False):
diff --git a/pytest.ini b/pytest.ini
index 26456fc87..61ceaf180 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,4 +1,6 @@
 [pytest]
 norecursedirs = '.*', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv', 'assets'
 testpaths = 'test'
-markers = docker_runner: Mark tests that require Docker to run.
+markers =
+   docker_runner: Mark tests that require Docker to run.
+   artifactory_ready: These tests can be run against a full Artifactory
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components_names.py b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components_names.py
index b146426e5..e292ecbc4 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components_names.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components_names.py
@@ -11,7 +11,6 @@ from conan.test.assets.sources import gen_function_h, gen_function_cpp
 from conan.test.utils.tools import TestClient
 
 
-@pytest.mark.slow
 @pytest.mark.tool("cmake")
 @pytest.fixture(scope="module")
 def setup_client_with_greetings():
@@ -433,7 +432,6 @@ def test_no_components(setup_client_with_greetings):
     create_chat(client, "none", package_info, cmake_find, test_cmake_find)
 
 
-@pytest.mark.slow
 @pytest.mark.tool("cmake")
 def test_same_names():
     client = TestClient()
@@ -621,7 +619,6 @@ class TestComponentsCMakeGenerators:
         client.run("install consumer.py", assert_error=True)
         assert "Component 'mypkg::zlib' not found in 'mypkg' package requirement" in client.out
 
-    @pytest.mark.slow
     def test_same_name_global_target_collision(self):
         # https://github.com/conan-io/conan/issues/7889
         conanfile_tpl = textwrap.dedent("""
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
index 8e73c2698..525b72c11 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
@@ -102,51 +102,103 @@ class TestRuntimeDirs:
 
 
 @pytest.mark.tool("cmake")
-@pytest.mark.parametrize("requires, tool_requires", [(True, False), (False, True), (True, True)])
-def test_cmaketoolchain_path_find_program(requires, tool_requires):
-    """Test that executables in bindirs of tool_requires can be found with
-    find_program() in consumer CMakeLists.
-    """
-    c = TestClient()
-
-    conanfile = textwrap.dedent("""
-        import os
-        from conan.tools.files import copy
-        from conan import ConanFile
-        class TestConan(ConanFile):
-            name = "tool"
-            version = "1.0"
-            exports_sources = "*"
-            def package(self):
-                copy(self, "*", self.source_folder, os.path.join(self.package_folder, "bin"))
-    """)
-    c.save({"conanfile.py": conanfile, "hello": "", "hello.exe": ""})
-    c.run("create .")
-
-    requires = 'requires = "tool/1.0"' if requires else ""
-    tool_requires = 'tool_requires = "tool/1.0"' if tool_requires else ""
-    conanfile = textwrap.dedent(f"""
-        from conan import ConanFile
-        from conan.tools.cmake import CMake
-        class PkgConan(ConanFile):
-            {requires}
-            {tool_requires}
-            settings = "os", "arch", "compiler", "build_type"
-            generators = "CMakeToolchain", "CMakeDeps"
-            def build(self):
-                cmake = CMake(self)
-                cmake.configure()
-    """)
-    consumer = textwrap.dedent("""
-        cmake_minimum_required(VERSION 3.15)
-        project(MyHello)
-        find_program(HELLOPROG hello)
-        if(HELLOPROG)
-            message(STATUS "Found hello prog: ${HELLOPROG}")
-        endif()
-    """)
-    c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
-    c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
-    assert "Found hello prog" in c.out
-    if requires and tool_requires:
-        assert "There is already a 'tool/1.0' package contributing to CMAKE_PROGRAM_PATH" in c.out
+class TestCMakeDepsPaths:
+
+    @pytest.mark.parametrize("requires, tool_requires", [(True, False), (False, True), (True, True)])
+    def test_find_program_path(self, requires, tool_requires):
+        """Test that executables in bindirs of tool_requires can be found with
+        find_program() in consumer CMakeLists.
+        """
+        c = TestClient()
+
+        conanfile = textwrap.dedent("""
+            import os
+            from conan.tools.files import copy
+            from conan import ConanFile
+            class TestConan(ConanFile):
+                name = "tool"
+                version = "1.0"
+                exports_sources = "*"
+                def package(self):
+                    copy(self, "*", self.source_folder, os.path.join(self.package_folder, "bin"))
+        """)
+        c.save({"conanfile.py": conanfile, "hello": "", "hello.exe": ""})
+        c.run("create .")
+
+        requires = 'requires = "tool/1.0"' if requires else ""
+        tool_requires = 'tool_requires = "tool/1.0"' if tool_requires else ""
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            from conan.tools.cmake import CMake
+            class PkgConan(ConanFile):
+                {requires}
+                {tool_requires}
+                settings = "os", "arch", "compiler", "build_type"
+                generators = "CMakeToolchain", "CMakeDeps"
+                def build(self):
+                    cmake = CMake(self)
+                    cmake.configure()
+        """)
+        consumer = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.15)
+            project(MyHello)
+            find_program(HELLOPROG hello)
+            if(HELLOPROG)
+                message(STATUS "Found hello prog: ${HELLOPROG}")
+            endif()
+        """)
+        c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
+        c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
+        assert "Found hello prog" in c.out
+        if requires and tool_requires:
+            assert "There is already a 'tool/1.0' package contributing to CMAKE_PROGRAM_PATH" in c.out
+
+    def test_find_include_and_lib_paths(self):
+        c = TestClient()
+        conanfile = textwrap.dedent("""
+            import os
+            from conan.tools.files import copy
+            from conan import ConanFile
+            class TestConan(ConanFile):
+                name = "hello"
+                version = "1.0"
+                exports_sources = "*"
+                def package(self):
+                    copy(self, "*.h", self.source_folder, os.path.join(self.package_folder, "include"))
+                    copy(self, "*.lib", self.source_folder, os.path.join(self.package_folder, "lib"))
+                    copy(self, "*.a", self.source_folder, os.path.join(self.package_folder, "lib"))
+                    copy(self, "*.so", self.source_folder, os.path.join(self.package_folder, "lib"))
+                    copy(self, "*.dll", self.source_folder, os.path.join(self.package_folder, "lib"))
+        """)
+        c.save({"conanfile.py": conanfile,
+                "hello.h": "", "hello.lib": "", "libhello.a": "",
+                "libhello.so": "", "libhello.dll": ""
+        })
+        c.run("create .")
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            from conan.tools.cmake import CMake
+            class PkgConan(ConanFile):
+                requires = "hello/1.0"
+                settings = "os", "arch", "compiler", "build_type"
+                generators = "CMakeToolchain", "CMakeDeps"
+                def build(self):
+                    cmake = CMake(self)
+                    cmake.configure()
+        """)
+        consumer = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.15)
+            project(MyHello)
+            find_file(HELLOINC hello.h)
+            find_library(HELLOLIB hello)
+            if(HELLOINC)
+                message(STATUS "Found hello header: ${HELLOINC}")
+            endif()
+            if(HELLOLIB)
+                message(STATUS "Found hello lib: ${HELLOLIB}")
+            endif()
+        """)
+        c.save({"conanfile.py": conanfile, "CMakeLists.txt": consumer}, clean_first=True)
+        c.run(f"build . -c tools.cmake.cmakedeps:new={new_value}")
+        assert "Found hello header" in c.out
+        assert "Found hello lib" in c.out
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_link_order.py b/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
index cab212b9c..61f7332e9 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
@@ -262,7 +262,7 @@ def _get_link_order_from_xcode(content):
     libs = []
 
     # Find the right Release block in the XCode file
-    results = re.finditer('/\* Release \*/ = {', content)
+    results = re.finditer(r'/\* Release \*/ = {', content)
     for r in results:
         release_section = content[r.start():].split("name = Release;", 1)[0]
         if "-headerpad_max_install_names" in release_section:
diff --git a/test/functional/toolchains/cmake/test_cmake.py b/test/functional/toolchains/cmake/test_cmake.py
index b235ba49a..fe10c79f4 100644
--- a/test/functional/toolchains/cmake/test_cmake.py
+++ b/test/functional/toolchains/cmake/test_cmake.py
@@ -488,7 +488,7 @@ def test_msvc_vs_versiontoolset():
                     cmake = CMake(self)
                     cmake.configure()
                     cmake.build()
-                    self.run("Release\\myapp.exe")
+                    self.run(r"Release\\myapp.exe")
             """)
     cmakelists = gen_cmakelists(appname="myapp", appsources=["app.cpp"])
     main = gen_function_cpp(name="main")
diff --git a/test/functional/toolchains/microsoft/test_msbuilddeps.py b/test/functional/toolchains/microsoft/test_msbuilddeps.py
index c69139d1a..6de683814 100644
--- a/test/functional/toolchains/microsoft/test_msbuilddeps.py
+++ b/test/functional/toolchains/microsoft/test_msbuilddeps.py
@@ -409,7 +409,6 @@ if "17" in tools_locations['visual_studio'] and not tools_locations['visual_stud
 @pytest.mark.skipif(platform.system() != "Windows", reason="Requires MSBuild")
 class MSBuildGeneratorTest(unittest.TestCase):
 
-    @pytest.mark.slow
     @pytest.mark.tool("cmake")
     def test_msbuild_generator(self):
         client = TestClient()
diff --git a/test/integration/command_v2/test_info_build_order.py b/test/integration/command_v2/test_info_build_order.py
index ef7ec1346..45e8e37bc 100644
--- a/test/integration/command_v2/test_info_build_order.py
+++ b/test/integration/command_v2/test_info_build_order.py
@@ -805,3 +805,62 @@ def test_build_order_space_in_options():
     order = json.loads(tc.load("order.json"))
     assert order["order"][0][0]["build_args"] == '''--requires=dep/1.0 --build=dep/1.0 -o="dep/*:extras=cxx="yes" gnuext='no'" -o="dep/*:flags=define=FOO define=BAR define=BAZ"'''
 
+
+def test_build_order_build_context_compatible():
+    c = TestClient()
+    foo = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.build import check_min_cppstd
+
+        class Pkg(ConanFile):
+            name = "foo"
+            version = "1.0"
+            settings = "os", "compiler"
+            def validate_build(self):
+                check_min_cppstd(self, 17)
+
+            def validate(self):
+                check_min_cppstd(self, 14)
+        """)
+    bar = GenConanfile("bar", "1.0").with_settings("os", "compiler").with_tool_requirement("foo/1.0")
+
+    profile = textwrap.dedent("""
+        [settings]
+        compiler=gcc
+        compiler.cppstd=gnu14
+        compiler.libcxx=libstdc++11
+        compiler.version=11
+        os=Linux
+        """)
+    c.save({"conanfile_foo.py": foo,
+            "conanfile_bar.py": bar,
+            "profile": profile})
+    c.run("export conanfile_foo.py")
+    c.run("export conanfile_bar.py")
+
+    #  "--require/bar.1.0" and "require=foo/1.0" along with `--build=missing` would cause both
+    # packages to be built in the host context - with foo being built with cppstd=17
+    # (because the default cppstd=14 is not enough) bar requires foo in the "build" context -
+    # where cppstd=14 - but it can reuse the one built for cppstd=17
+    # (via compatibility plugin)
+
+    # The three approaches are equivalent:
+    #  - Using "--build=missing" and forcing  "-s foo/*:compiler.cppstd=17", means, build missing
+    #    binaries, and for foo I want the compiler.cppstd=17 binary. This approach could build other
+    #    missing binaries too
+    #  - Using "--build=compatible:foo/*" and "--build=missing:bar/*" means, build only missing
+    #    binary for bar, and for "foo", build a compatible one if the main one is missing. This
+    #    approach prevents other packages (not foo/bar) from accidentally being built.
+    #  - Last approach, passing both "--build=missing:foo/*" and "--build=compatible:foo/*" is
+    #    similar to the other two in final behavior, but the --build=missing:foo avoids doing the
+    #    compatibility check for consumption of "foo", and goes directly to the build check
+
+    for approach in ("--build=missing -s foo/*:compiler.cppstd=17",
+                     '--build="compatible:foo/*" --build="missing:bar/*"',
+                     '--build="missing:foo/*" --build="compatible:foo/*" --build="missing:bar/*"'):
+        c.run(f'graph build-order --require=foo/1.0 --require=bar/1.0 -pr:a profile {approach}')
+        c.assert_listed_binary({"foo/1.0": ["4e2ae338231ae18d0d43b9e119404d2b2c416758", "Build"],
+                                "bar/1.0": ["5e4ffcc1ff33697a4ee96f66f0d2228ec458f25c", "Build"]})
+        c.assert_listed_binary({"foo/1.0": ["4e2ae338231ae18d0d43b9e119404d2b2c416758", "Build"]},
+                               build=True)
+
diff --git a/test/integration/configuration/test_profile_jinja.py b/test/integration/configuration/test_profile_jinja.py
index a985f992c..dc3d3daf3 100644
--- a/test/integration/configuration/test_profile_jinja.py
+++ b/test/integration/configuration/test_profile_jinja.py
@@ -269,3 +269,41 @@ def test_profile_jinja_error():
     c.save({"profile1": "{% set kk = other() %}"})
     c.run("profile show -pr=profile1", assert_error=True)
     assert "ERROR: Error while rendering the profile template file" in c.out
+
+
+def test_profile_macro_per_package():
+    client = TestClient()
+    tpl1 = textwrap.dedent("""
+        {% macro set_windows_settings(lib) %}
+        {{lib}}/*:os=Windows
+        {{lib}}/*:arch=x86
+        {% endmacro %}
+        {% macro set_windows_conf(lib) %}
+        {{lib}}/*:user.conf:key = 2
+        {% endmacro %}
+
+        [settings]
+        os = Linux
+        {{set_windows_settings("mypkg")}}
+        [conf]
+        user.conf:key = 1
+        {{set_windows_conf("mypkg")}}
+        """)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            name = "mypkg"
+            version = "0.1"
+            settings = "os", "arch"
+            def generate(self):
+                value = self.conf.get("user.conf:key")
+                self.output.info(f"os={self.settings.os}!!")
+                self.output.info(f"arch={self.settings.arch}!!")
+                self.output.info(f"user.conf:key={value}!!!!")
+        """)
+    client.save({"conanfile.py": conanfile,
+                 "profile1": tpl1})
+    client.run("install . -pr=profile1")
+    assert "conanfile.py (mypkg/0.1): user.conf:key=2!!!!" in client.out
+    assert "conanfile.py (mypkg/0.1): os=Windows!!" in client.out
+    assert "conanfile.py (mypkg/0.1): arch=x86!!" in client.out
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
new file mode 100644
index 000000000..4b6001749
--- /dev/null
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -0,0 +1,92 @@
+import re
+import textwrap
+
+from conan.test.utils.tools import TestClient
+
+new_value = "will_break_next"
+
+
+def test_cmakedeps_direct_deps_paths():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        import os
+        from conan.tools.files import copy
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            name = "lib"
+            version = "1.0"
+            def package_info(self):
+                self.cpp_info.includedirs = ["myincludes"]
+                self.cpp_info.libdirs = ["mylib"]
+    """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .")
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        from conan.tools.cmake import CMake
+        class PkgConan(ConanFile):
+            requires = "lib/1.0"
+            settings = "os", "arch", "compiler", "build_type"
+            generators = "CMakeDeps"
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+    """)
+    c.save({"conanfile.py": conanfile}, clean_first=True)
+    c.run(f"install . -c tools.cmake.cmakedeps:new={new_value}")
+    cmake_paths = c.load("conan_cmakedeps_paths.cmake")
+    assert re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH \".*/bin\"", cmake_paths)  # default
+    assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/mylib\"", cmake_paths)
+    assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/myincludes\"", cmake_paths)
+
+
+def test_cmakedeps_transitive_paths():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        import os
+        from conan.tools.files import copy
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            name = "liba"
+            version = "1.0"
+            def package_info(self):
+                self.cpp_info.includedirs = ["includea"]
+                self.cpp_info.libdirs = ["liba"]
+                self.cpp_info.bindirs = ["bina"]
+    """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .")
+    conanfile = textwrap.dedent("""
+        import os
+        from conan.tools.files import copy
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            name = "libb"
+            version = "1.0"
+            requires = "liba/1.0"
+            def package_info(self):
+                self.cpp_info.includedirs = ["includeb"]
+                self.cpp_info.libdirs = ["libb"]
+                self.cpp_info.bindirs = ["binb"]
+    """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .")
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        from conan.tools.cmake import CMake
+        class PkgConan(ConanFile):
+            requires = "libb/1.0"
+            settings = "os", "arch", "compiler", "build_type"
+            generators = "CMakeDeps"
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+    """)
+    c.save({"conanfile.py": conanfile}, clean_first=True)
+    c.run(f"install . -c tools.cmake.cmakedeps:new={new_value}")
+    cmake_paths = c.load("conan_cmakedeps_paths.cmake")
+    cmake_paths.replace("\\", "/")
+    assert re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH \".*/libb.*/p/binb\"\)", cmake_paths)
+    assert not re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH /bina\"", cmake_paths)
+    assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/libb.*/p/libb\" \".*/liba.*/p/liba\"\)", cmake_paths)
+    assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/libb.*/p/includeb\" \".*/liba.*/p/includea\"\)", cmake_paths)
diff --git a/test/unittests/model/version/test_version_comparison.py b/test/unittests/model/version/test_version_comparison.py
index f35cd9796..a0d6341b9 100644
--- a/test/unittests/model/version/test_version_comparison.py
+++ b/test/unittests/model/version/test_version_comparison.py
@@ -1,6 +1,7 @@
 import pytest
 
 from conan.internal.model.version import Version
+from conan.internal.model.version_range import VersionRange
 
 v = [("1", "2"),
      ("1.0", "1.1"),
@@ -155,3 +156,11 @@ def test_elem_comparison():
     assert micro > 3
     assert micro < 5
     assert micro == 4
+
+def test_version_in_range():
+    v = Version("1.0")
+    assert not v.in_range(">1.0 <2")
+    assert v.in_range(">=1.0 <2")
+
+    assert not Version("1.0-rc").in_range(">=1.0 <2.0")
+    assert Version("1.0-rc").in_range(">=1.0 <2.0", resolve_prerelease=True)