Repository: conan-io/conan
Pull request #17970 (2025-03-15 18:09:21)

Tests for cstd and qbs in CI
Changelog: Omit
Docs: Omit

Adding tests:

- for cstd
- qbs in CI

Instead of https://github.com/conan-io/conan/pull/17968


memsharded: Hi @ABBAPOH

I was trying to add tests for CI in this PR, but it happens that tests are not passing.
It seems that the ``apt install qbs`` version is 1.X, and it is not supporting some of the arguments such as ``--jobs``.
But installing the latest 2.6 would require to build from source, and that sounds a bit too much.

Any suggestion?
ABBAPOH: What Linux version do you use?

There is a somewhat hacky way to install Qbs a long with QtCreator using the script we use in our CI

https://github.com/qbs/qbs/blob/master/scripts/install-qt.sh
An example is here

https://github.com/qbs/qbs/blob/master/.github/actions/download-qtc/action.yml#L13
The newest QtCreator version is 15.0.1 and it is shipped with Qbs 2.6.
The script prints the binary for to stdout and the output can be simply added to PATH
memsharded: > There is a somewhat hacky way to install Qbs a long with QtCreator using the script we use in our CI

We are using a docker ``FROM ubuntu:24.04``
This is the thing I was trying to avoid, I thought that installing the full Qt/QtCreator was overkill, and it was going to make a lot heavier the docker image unnecessarily.

Isn't it possible to get a prebuilt qbs 2.6.0 without Qt/QtCreator?
If it isn't relatively easy and cheap to bring it to our CI (in Linux, the other systems are already a bit too slow), I'd probably drop it at the moment, it isn't great, but probably the right trade-off by now.

ABBAPOH: Well, there is a Linux archive that is built by our CI as a part of release process https://download.qt.io/official_releases/qbs/2.6.0/qbs-linux-x86_64-2.6.0.tar.gz, it is built on Ubuntu 24.04, I'll try to write a simple docker image as an example to see if it works
memsharded: > Well, there is a Linux archive that is built by our CI as a part of release process https://download.qt.io/official_releases/qbs/2.6.0/qbs-linux-x86_64-2.6.0.tar.gz (about 30mb), it is built on Ubuntu 24.04, I'll try to write a simple docker image as an example to see if it works

Nice! That could be helpful, I'll also try to give it a go and see what happens. Thanks for the pointer!
ABBAPOH: something like that works
full image https://pastebin.com/83Mg52i3. the [entrypoint](https://github.com/qbs/qbs/blob/master/docker/entrypoint.sh) shenanigans can be omitted I suppose
```
RUN apt-get update -qq && \
    DEBIAN_FRONTEND="noninteractive" apt-get install -qq -y --no-install-recommends \
        build-essential \
        curl \
        libglib2.0-0t64 \
        locales

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

RUN cd /tmp && \
    mkdir qbs && \
    cd qbs && \
    curl -L https://download.qt.io/official_releases/qbs/2.6.0/qbs-linux-x86_64-2.6.0.tar.gz > qbs-linux-x86_64-2.6.0.tar.gz && \
    tar -xzf qbs-linux-x86_64-2.6.0.tar.gz && \
    mv qbs-linux-x86_64-2.6.0 /opt/qbs && \
    rm qbs-linux-x86_64-2.6.0.tar.gz

ENV PATH=/opt/qbs/bin/:${PATH}

USER $USER_NAME

RUN qbs-setup-toolchains /usr/bin/g++ gcc && \
    qbs config defaultProfile qt-gcc_64

# Switch back to root user for the entrypoint script.
USER root

# Work-around for QTBUG-79020
RUN echo "export QT_NO_GLIB=1" >> /etc/profile.d/qt.sh
```

Locale is required to silence Qt warning saying that C locale is not UTF-8 (maybe can be silenced via some env var).
libglib is also required to run Qt event loop.
Gcc is installed for testing purposes to compile an example

Patch:
diff --git a/conan/internal/api/audit/providers.py b/conan/internal/api/audit/providers.py
index 74b35edc6..aa19b66c0 100644
--- a/conan/internal/api/audit/providers.py
+++ b/conan/internal/api/audit/providers.py
@@ -58,7 +58,7 @@ class ConanCenterProvider:
                 # TODO: How to report auth error to the user
                 ConanOutput().error(f"Authentication error ({response.status_code}).\n"
                                     f"Your token may be invalid or not yet validated. If you recently registered, please check your email to validate your token.\n"
-                                    f" - Set a valid token using: 'conan audit provider auth --name={self.name} --token=<your_token>'\n"
+                                    f" - Set a valid token using: 'conan audit provider auth {self.name} --token=<your_token>'\n"
                                     f" - If you don’t have a token, register at: https://audit.conan.io/register")
 
                 errors_in_response = True
diff --git a/conan/internal/api/uploader.py b/conan/internal/api/uploader.py
index 8d8b8a4d9..ce057893e 100644
--- a/conan/internal/api/uploader.py
+++ b/conan/internal/api/uploader.py
@@ -181,7 +181,7 @@ class PackagePreparator:
             clean_dirty(package_tgz)
 
         # Get all the files in that directory
-        # existing package, will use short paths if defined
+        # existing package
         package_folder = layout.package()
         files, symlinked_folders = gather_files(package_folder)
         files.update(symlinked_folders)
diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py
index 52a021817..4b0271891 100644
--- a/conan/tools/apple/apple.py
+++ b/conan/tools/apple/apple.py
@@ -6,10 +6,10 @@ from conan.tools.build import cmd_args_to_string
 from conan.errors import ConanException
 
 
-def is_apple_os(conanfile):
+def is_apple_os(conanfile, build_context=False):
     """returns True if OS is Apple one (Macos, iOS, watchOS, tvOS or visionOS)"""
-    os_ = conanfile.settings.get_safe("os")
-    return str(os_) in ['Macos', 'iOS', 'watchOS', 'tvOS', 'visionOS']
+    settings = conanfile.settings_build if build_context else conanfile.settings
+    return str(settings.get_safe("os")) in ['Macos', 'iOS', 'watchOS', 'tvOS', 'visionOS']
 
 
 def _to_apple_arch(arch, default=None):
diff --git a/conan/tools/meson/helpers.py b/conan/tools/meson/helpers.py
index 5f275849e..d97ab985c 100644
--- a/conan/tools/meson/helpers.py
+++ b/conan/tools/meson/helpers.py
@@ -68,6 +68,17 @@ _cppstd_map = {
 }
 
 
+def get_apple_subsystem(apple_sdk):
+    return {
+        "iphoneos": "ios",
+        "iphonesimulator": "ios-simulator",
+        "appletvos": "tvos",
+        "appletvsimulator": "tvos-simulator",
+        "watchos": "watchos",
+        "watchsimulator": "watchos-simulator",
+    }.get(apple_sdk, "macos")
+
+
 def to_meson_machine(machine_os, machine_arch):
     """Gets the OS system info as the Meson machine context.
 
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index 62b9213dd..88bbc9b73 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -12,6 +12,7 @@ from conan.tools.build.cross_building import cross_building
 from conan.tools.build.flags import libcxx_flags, architecture_flag
 from conan.tools.env import VirtualBuildEnv
 from conan.tools.meson.helpers import *
+from conan.tools.meson.helpers import get_apple_subsystem
 from conan.tools.microsoft import VCVars, msvc_runtime_flag
 from conans.util.files import save
 
@@ -137,6 +138,9 @@ class MesonToolchain:
     {% for context, values in cross_build.items() %}
     [{{context}}_machine]
     system = '{{values["system"]}}'
+    {% if values.get("subsystem") %}
+    subsystem = '{{values["subsystem"]}}'
+    {% endif %}
     cpu_family = '{{values["cpu_family"]}}'
     cpu = '{{values["cpu"]}}'
     endian = '{{values["endian"]}}'
@@ -242,6 +246,15 @@ class MesonToolchain:
             arch_build = conanfile.settings_build.get_safe('arch')
             self.cross_build["build"] = to_meson_machine(os_build, arch_build)
             self.cross_build["host"] = to_meson_machine(os_host, arch_host)
+            # Check subsystem if it's Apple cross-building only. It requires Meson >= 1.2.0,
+            # but it does not break lower versions.
+            # See https://mesonbuild.com/Reference-tables.html#subsystem-names-since-120
+            # Issue: https://github.com/conan-io/conan/issues/17873
+            if self._is_apple_system and is_apple_os(conanfile, build_context=True):
+                sdk_build = conanfile.settings_build.get_safe("os.sdk")
+                sdk_host = conanfile.settings.get_safe("os.sdk")
+                self.cross_build["host"]["subsystem"] = get_apple_subsystem(sdk_host)
+                self.cross_build["build"]["subsystem"] = get_apple_subsystem(sdk_build)
             self.properties["needs_exe_wrapper"] = True
             if hasattr(conanfile, 'settings_target') and conanfile.settings_target:
                 settings_target = conanfile.settings_target
diff --git a/conans/client/installer.py b/conans/client/installer.py
index 5ffd1faf5..d661ac53c 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -84,10 +84,7 @@ class _PackageBuilder:
             try:
                 shutil.copytree(source_folder, build_folder, symlinks=True)
             except Exception as e:
-                msg = str(e)
-                if "206" in msg:  # System error shutil.Error 206: Filename or extension too long
-                    msg += "\nUse short_paths=True if paths too long"
-                raise ConanException("%s\nError copying sources to build folder" % msg)
+                raise ConanException(f"{e}\nError copying sources to build folder")
 
     def _build(self, conanfile, pref):
         write_generators(conanfile, self._hook_manager, self._home_folder)


Test patch:
diff --git a/.ci/docker/conan-tests b/.ci/docker/conan-tests
index 1ee57ab22..88b979455 100644
--- a/.ci/docker/conan-tests
+++ b/.ci/docker/conan-tests
@@ -155,6 +155,16 @@ RUN wget https://github.com/premake/premake-core/releases/download/v5.0.0-beta4/
     tar -xvzf premake-5.0.0-beta4-linux.tar.gz && chmod +x premake5 && mkdir /usr/share/premake && \
     mv premake5 /usr/share/premake
 
+RUN cd /tmp && \
+    mkdir qbs && \
+    cd qbs && \
+    curl -L https://download.qt.io/official_releases/qbs/2.6.0/qbs-linux-x86_64-2.6.0.tar.gz > qbs-linux-x86_64-2.6.0.tar.gz && \
+    tar -xzf qbs-linux-x86_64-2.6.0.tar.gz && \
+    mv qbs-linux-x86_64-2.6.0 /usr/share/qbs && \
+    rm qbs-linux-x86_64-2.6.0.tar.gz
+
+RUN echo "export QT_NO_GLIB=1" >> /etc/profile.d/qt.sh
+
 USER conan
 WORKDIR $HOME
 
diff --git a/test/conftest.py b/test/conftest.py
index 9c9c76f78..4583c44fc 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -178,7 +178,13 @@ tools_locations = {
             # 'Windows': os.getenv("ANDROID_NDK_HOME"),
         }
     },
-    "qbs": {"disabled": True},
+    "qbs": {
+        "exe": "qbs",
+        "default": "2.6.0",
+        "2.6.0": {
+            "path": {'Linux': '/usr/share/qbs/bin'}
+        }
+    },
     # TODO: Intel oneAPI is not installed in CI yet. Uncomment this line whenever it's done.
     # "intel_oneapi": {
     #     "default": "2021.3",
diff --git a/test/functional/toolchains/qbs/test_qbs.py b/test/functional/toolchains/qbs/test_qbs.py
index 52ea80635..b86ddfdbb 100644
--- a/test/functional/toolchains/qbs/test_qbs.py
+++ b/test/functional/toolchains/qbs/test_qbs.py
@@ -1,4 +1,6 @@
 import os
+import platform
+
 import pytest
 import textwrap
 
@@ -13,10 +15,8 @@ def gen_file(template, **context):
     return t.render(**context)
 
 
-@pytest.mark.parametrize('shared', [
-    ('False'),
-    ('True'),
-])
+@pytest.mark.parametrize('shared', [False, True])
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_api_qbs_create_lib(shared):
     client = TestClient()
@@ -25,6 +25,7 @@ def test_api_qbs_create_lib(shared):
     assert "compiling hello.cpp" in client.out
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbs_all_products():
     client = TestClient()
@@ -65,6 +66,7 @@ def test_qbs_all_products():
     assert "--all-products" in client.out
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbs_specific_products():
     client = TestClient()
@@ -105,6 +107,7 @@ def test_qbs_specific_products():
     assert "--products hello,hello" in client.out
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbs_multiple_configurations():
     client = TestClient()
diff --git a/test/functional/toolchains/qbs/test_qbsdeps.py b/test/functional/toolchains/qbs/test_qbsdeps.py
index 8302397be..9cdef8101 100644
--- a/test/functional/toolchains/qbs/test_qbsdeps.py
+++ b/test/functional/toolchains/qbs/test_qbsdeps.py
@@ -1,3 +1,4 @@
+import platform
 
 import pytest
 
@@ -25,6 +26,7 @@ def gen_qbs_application(**context):
     return t.render(**context)
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbsdeps_with_test_requires_header_only():
     client = TestClient()
@@ -70,6 +72,7 @@ def test_qbsdeps_with_test_requires_header_only():
     client.run_command("qbs build moduleProviders.conan.installDirectory:.")
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbsdeps_with_test_requires_lib():
     client = TestClient()
@@ -94,6 +97,7 @@ def test_qbsdeps_with_test_requires_lib():
     client.run_command("qbs build moduleProviders.conan.installDirectory:.")
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbsdeps_with_qbs_toolchain():
     client = TestClient()
diff --git a/test/functional/toolchains/qbs/test_qbsprofile.py b/test/functional/toolchains/qbs/test_qbsprofile.py
index 93841cafc..dc3e20cd7 100644
--- a/test/functional/toolchains/qbs/test_qbsprofile.py
+++ b/test/functional/toolchains/qbs/test_qbsprofile.py
@@ -1,3 +1,4 @@
+import platform
 import textwrap
 import pytest
 
@@ -5,6 +6,7 @@ from conan.test.utils.tools import TestClient
 from conan.test.assets.sources import gen_function_cpp
 
 
+@pytest.mark.skipif(platform.system() != "Linux", reason="QBS only for Linux now")
 @pytest.mark.tool("qbs")
 def test_qbsprofile():
     client = TestClient()
diff --git a/test/integration/command/info/test_info_folders.py b/test/integration/command/info/test_info_folders.py
index 464a02e65..8ad2c3f4b 100644
--- a/test/integration/command/info/test_info_folders.py
+++ b/test/integration/command/info/test_info_folders.py
@@ -13,7 +13,6 @@ from conan import ConanFile
 class AConan(ConanFile):
     name = "package"
     version = "0.1.0"
-    short_paths=False
 """
 
 with_deps_path_file = """
diff --git a/test/integration/py_requires/python_requires_test.py b/test/integration/py_requires/python_requires_test.py
index 48d90f981..232f389fb 100644
--- a/test/integration/py_requires/python_requires_test.py
+++ b/test/integration/py_requires/python_requires_test.py
@@ -317,7 +317,6 @@ class PyRequiresExtendTest(unittest.TestCase):
                 author = "author@company.com"
                 exports = "*.txt"
                 exports_sources = "*.h"
-                short_paths = True
                 generators = "CMakeToolchain"
             """)
         client.save({"conanfile.py": conanfile,
@@ -334,7 +333,6 @@ class PyRequiresExtendTest(unittest.TestCase):
                 def build(self):
                     self.output.info("Exports sources! %s" % self.exports_sources)
                     self.output.info("HEADER CONTENT!: %s" % load(self, "header.h"))
-                    self.output.info("Short paths! %s" % self.short_paths)
                     self.output.info("License! %s" % self.license)
                     self.output.info("Author! %s" % self.author)
                     assert os.path.exists("conan_toolchain.cmake")
@@ -346,7 +344,6 @@ class PyRequiresExtendTest(unittest.TestCase):
         self.assertIn("pkg/0.1@user/testing: Exports sources! *.h", client.out)
         self.assertIn("pkg/0.1@user/testing: Copied 1 '.txt' file: other.txt", client.out)
         self.assertIn("pkg/0.1@user/testing: Copied 1 '.h' file: header.h", client.out)
-        self.assertIn("pkg/0.1@user/testing: Short paths! True", client.out)
         self.assertIn("pkg/0.1@user/testing: License! MyLicense", client.out)
         self.assertIn("pkg/0.1@user/testing: Author! author@company.com", client.out)
         self.assertIn("pkg/0.1@user/testing: HEADER CONTENT!: pkg new header contents", client.out)
diff --git a/test/integration/toolchains/meson/test_mesontoolchain.py b/test/integration/toolchains/meson/test_mesontoolchain.py
index e5cb72953..06df19bd6 100644
--- a/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -68,6 +68,71 @@ def test_apple_meson_keep_user_custom_flags():
     assert "cpp_link_args = ['-isysroot', '/other/sdk/path', '-arch', 'myarch', '-otherminversion=10.7', '-stdlib=libc++']" in content
 
 
+def test_apple_meson_cross_building_subsystem():
+    """
+    Issue related: https://github.com/conan-io/conan/issues/17873
+    """
+    default = textwrap.dedent("""
+    [settings]
+    os=Macos
+    arch=x86_64
+    compiler=apple-clang
+    compiler.version=12.0
+    compiler.libcxx=libc++
+    build_type=Release
+    """)
+    cross = textwrap.dedent("""
+    [settings]
+    os = iOS
+    os.version = 10.0
+    os.sdk = iphoneos
+    arch = armv8
+    compiler = apple-clang
+    compiler.version = 12.0
+    compiler.libcxx = libc++
+
+    [conf]
+    tools.apple:sdk_path=/my/sdk/path
+    """)
+    t = TestClient()
+    t.save({"conanfile.py": GenConanfile(name="app", version="1.0")
+                            .with_settings("os", "arch", "compiler", "build_type")
+                            .with_generator("MesonToolchain"),
+            "build": default,
+            "host": cross})
+    t.run("install . -pr:h host -pr:b build")
+    content = t.load(MesonToolchain.cross_filename)
+    machines_settings = textwrap.dedent("""\
+    [build_machine]
+    system = 'darwin'
+    subsystem = 'macos'
+    cpu_family = 'x86_64'
+    cpu = 'x86_64'
+    endian = 'little'
+    [host_machine]
+    system = 'darwin'
+    subsystem = 'ios'
+    cpu_family = 'aarch64'
+    cpu = 'armv8'
+    endian = 'little'""")
+    assert machines_settings in content
+    # Let's check that it does not appear if cross-compiling to other non-Apple-OS
+    cross = textwrap.dedent("""
+    [settings]
+    arch=armv8
+    build_type=Release
+    compiler=gcc
+    compiler.cppstd=gnu17
+    compiler.libcxx=libstdc++11
+    compiler.version=13
+    os=Linux
+    """)
+    t.save({"host": cross})
+    t.run("install . -pr:h host -pr:b build")
+    content = t.load(MesonToolchain.cross_filename)
+    assert "subsystem =" not in content
+
+
 def test_extra_flags_via_conf():
     profile = textwrap.dedent("""
         [settings]
diff --git a/test/unittests/client/conanfile_loader_test.py b/test/unittests/client/conanfile_loader_test.py
index 9bfd97d8d..c27fc2655 100644
--- a/test/unittests/client/conanfile_loader_test.py
+++ b/test/unittests/client/conanfile_loader_test.py
@@ -12,30 +12,6 @@ from conan.test.utils.test_files import temp_folder
 from conans.util.files import save, chdir
 
 
-class ConanLoaderTest(unittest.TestCase):
-
-    def test_inherit_short_paths(self):
-        loader = ConanFileLoader(None)
-
-        tmp_dir = temp_folder()
-        conanfile_path = os.path.join(tmp_dir, "conanfile.py")
-        conanfile = """from base_recipe import BasePackage
-class Pkg(BasePackage):
-    pass
-"""
-        base_recipe = """from conan import ConanFile
-class BasePackage(ConanFile):
-    short_paths = True
-"""
-        save(conanfile_path, conanfile)
-        save(os.path.join(tmp_dir, "base_recipe.py"), base_recipe)
-        conan_file = loader.load_basic(conanfile_path)
-        self.assertEqual(conan_file.short_paths, True)
-
-        result = loader.load_consumer(conanfile_path)
-        self.assertEqual(result.short_paths, True)
-
-
 class ConanLoaderTxtTest(unittest.TestCase):
     def test_conanfile_txt_errors(self):
         # Invalid content
diff --git a/test/unittests/client/tools/cppstd_required_test.py b/test/unittests/client/tools/cppstd_required_test.py
index 8da43ebb8..177449daa 100644
--- a/test/unittests/client/tools/cppstd_required_test.py
+++ b/test/unittests/client/tools/cppstd_required_test.py
@@ -1,8 +1,11 @@
 import unittest
+
+import pytest
 from mock import mock
 from parameterized import parameterized
 
-from conan.tools.build import check_max_cppstd, check_min_cppstd, valid_max_cppstd, valid_min_cppstd
+from conan.tools.build import check_max_cppstd, check_min_cppstd, valid_max_cppstd, valid_min_cppstd, \
+    valid_min_cstd, valid_max_cstd
 from conan.test.utils.mocks import MockSettings, ConanFileMock
 from conan.errors import ConanException, ConanInvalidConfiguration
 
@@ -235,3 +238,46 @@ class ValidMaxCppstdTests(unittest.TestCase):
 
             conanfile.settings.values["compiler.cppstd"] = "98"
             self.assertFalse(valid_max_cppstd(conanfile, cppstd, True))
+
+
+class TestCstd:
+
+    @pytest.mark.parametrize("compiler, cstd, mincstd, valid",
+                             [["gcc",  "99", "99", True],
+                              ["gcc",  "gnu99", "99", True],
+                              ["gcc", "99", "11", False]
+                              ])
+    def test_valid_min(self, compiler, cstd, mincstd, valid):
+        settings = MockSettings({"arch": "x86_64",
+                                 "build_type": "Debug",
+                                 "os": "Linux",
+                                 "compiler": compiler,
+                                 "compiler.cstd": cstd})
+        conanfile = ConanFileMock(settings)
+        if valid:
+            assert valid_min_cstd(conanfile, mincstd, False)
+        else:
+            assert not valid_min_cstd(conanfile, mincstd, False)
+
+    @pytest.mark.parametrize("compiler, cstd, maxcstd, valid",
+                             [["gcc", "99", "11", True],
+                              ["gcc", "gnu99", "11", True],
+                              ["gcc", "17", "11", False]
+                              ])
+    def test_valid_max(self, compiler, cstd, maxcstd, valid):
+        settings = MockSettings({"arch": "x86_64",
+                                 "build_type": "Debug",
+                                 "os": "Linux",
+                                 "compiler": compiler,
+                                 "compiler.cstd": cstd})
+        conanfile = ConanFileMock(settings)
+        if valid:
+            assert valid_max_cstd(conanfile, maxcstd, False)
+        else:
+            assert not valid_max_cstd(conanfile, maxcstd, False)
+
+    def test_error(self):
+        conanfile = ConanFileMock()
+        with pytest.raises(ConanException) as e:
+            valid_min_cstd(conanfile, "potato")
+        assert "cstd parameter must be a number" in str(e.value)
diff --git a/test/unittests/tools/build/test_cstd.py b/test/unittests/tools/build/test_cstd.py
new file mode 100644
index 000000000..6ebf41622
--- /dev/null
+++ b/test/unittests/tools/build/test_cstd.py
@@ -0,0 +1,67 @@
+import pytest
+
+from conan.tools.build import supported_cstd, check_min_cstd
+from conan.errors import ConanException
+from conan.test.utils.mocks import MockSettings, ConanFileMock
+
+
+@pytest.mark.parametrize("compiler,compiler_version,values", [
+    ("clang", "2.1", ['99', 'gnu99']),
+    ("clang", "5", ['99', 'gnu99', '11', 'gnu11']),
+    ("clang", "17", ['99', 'gnu99', '11', 'gnu11', "17", "gnu17"]),
+    ("clang", "18", ['99', 'gnu99', '11', 'gnu11',  "17", "gnu17", "23", "gnu23"])
+])
+def test_supported_cstd_clang(compiler, compiler_version, values):
+    settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version})
+    conanfile = ConanFileMock(settings)
+    sot = supported_cstd(conanfile)
+    assert sot == values
+
+
+def test_supported_cstd_with_specific_values():
+    settings = MockSettings({})
+    conanfile = ConanFileMock(settings)
+    sot = supported_cstd(conanfile, "clang", "5")
+    assert sot == ['99', 'gnu99', '11', 'gnu11']
+
+
+def test_supported_cstd_error():
+    settings = MockSettings({})
+    conanfile = ConanFileMock(settings)
+    with pytest.raises(ConanException) as exc:
+        supported_cstd(conanfile)
+    assert "Called supported_cstd with no compiler or no compiler.version" in str(exc)
+
+
+@pytest.mark.parametrize("compiler,compiler_version,values", [
+    ("gcc", "4.6", ['99', 'gnu99']),
+    ("gcc", "5", ['99', 'gnu99', '11', 'gnu11']),
+    ("gcc", "13", ["99", "gnu99", "11", "gnu11", "17", "gnu17"]),
+    ("gcc", "14", ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"])
+])
+def test_supported_cppstd_gcc(compiler, compiler_version, values):
+    settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version})
+    conanfile = ConanFileMock(settings)
+    sot = supported_cstd(conanfile)
+    assert sot == values
+
+
+@pytest.mark.parametrize("compiler,compiler_version,values", [
+    ("msvc", "191", []),
+    ("msvc", "193", ['11', '17']),
+])
+def test_supported_cstd_msvc(compiler, compiler_version, values):
+    settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version})
+    conanfile = ConanFileMock(settings)
+    sot = supported_cstd(conanfile)
+    assert sot == values
+
+
+def test_check_cstd_type():
+    """ cppstd must be a number
+    """
+    conanfile = ConanFileMock(MockSettings({}))
+    with pytest.raises(ConanException) as exc:
+        check_min_cstd(conanfile, "gnu17", False)
+
+    assert "cstd parameter must be a number", str(exc)
diff --git a/test/unittests/tools/meson/test_meson.py b/test/unittests/tools/meson/test_meson.py
index 4f80ea8af..6d97d0706 100644
--- a/test/unittests/tools/meson/test_meson.py
+++ b/test/unittests/tools/meson/test_meson.py
@@ -1,8 +1,11 @@
 import textwrap
 
+import pytest
+
 from conan.tools.meson import Meson
 from conan.internal.model.conf import ConfDefinition
 from conan.test.utils.mocks import ConanFileMock, MockSettings
+from conan.tools.meson.helpers import get_apple_subsystem
 
 
 def test_meson_build():
@@ -25,3 +28,16 @@ def test_meson_build():
     meson.build()
 
     assert '-j10' in str(conanfile.command)
+
+
+@pytest.mark.parametrize("apple_sdk, subsystem", [
+    ("iphoneos", "ios"),
+    ("iphonesimulator", "ios-simulator"),
+    ("appletvos", "tvos"),
+    ("appletvsimulator", "tvos-simulator"),
+    ("watchos", "watchos"),
+    ("watchsimulator", "watchos-simulator"),
+    (None, "macos")
+])
+def test_meson_subsystem_helper(apple_sdk, subsystem):
+    assert get_apple_subsystem(apple_sdk) == subsystem