Repository: conan-io/conan

Issue:
Run cmake wrapped with scan-build
I would like to be able to run cmake wrapped with clang's scan-build to perform a static analysis while building. However the CMake class hard-codes the name of the executable to be 'cmake'. It would be great if it were possible to override this.

Discussion:

Patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
index 40816f6b9..9bfcd123b 100644
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -21,7 +21,7 @@ class CMake(object):
 
     def __init__(self, conanfile, generator=None, cmake_system_name=True,
                  parallel=True, build_type=None, toolset=None, make_program=None,
-                 set_cmake_flags=False, msbuild_verbosity=None):
+                 set_cmake_flags=False, msbuild_verbosity=None, cmake_program=None):
         """
         :param conanfile: Conanfile instance
         :param generator: Generator name to use or none to autodetect
@@ -33,7 +33,8 @@ class CMake(object):
                 applies only to certain generators (e.g. Visual Studio)
         :param set_cmake_flags: whether or not to set CMake flags like CMAKE_CXX_FLAGS, CMAKE_C_FLAGS, etc.
                it's vital to set for certain projects (e.g. using CMAKE_SIZEOF_VOID_P or CMAKE_LIBRARY_ARCHITECTURE)
-        "param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator)
+        :param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator)
+        :param cmake_program: Path to the custom cmake executable
         """
         if not isinstance(conanfile, ConanFile):
             raise ConanException("First argument of CMake() has to be ConanFile. Use CMake(self)")
@@ -41,6 +42,7 @@ class CMake(object):
         self._conanfile = conanfile
         self._settings = conanfile.settings
         self._build_type = build_type or conanfile.settings.get_safe("build_type")
+        self._cmake_program = os.getenv("CONAN_CMAKE_PROGRAM") or cmake_program or "cmake"
 
         self.generator = generator or get_generator(conanfile.settings)
         self.parallel = parallel
@@ -187,7 +189,7 @@ class CMake(object):
             pkg_env = {"PKG_CONFIG_PATH": self._conanfile.install_folder} if set_env else {}
 
         with tools.environment_append(pkg_env):
-            command = "cd %s && cmake %s" % (args_to_string([self.build_dir]), arg_list)
+            command = "cd %s && %s %s" % (args_to_string([self.build_dir]), self._cmake_program, arg_list)
             if platform.system() == "Windows" and self.generator == "MinGW Makefiles":
                 with tools.remove_from_path("sh"):
                     self._run(command)
@@ -230,7 +232,7 @@ class CMake(object):
             self.build_config,
             args_to_string(args)
         ])
-        command = "cmake --build %s" % arg_list
+        command = "%s --build %s" % (self._cmake_program, arg_list)
         self._run(command)
 
     def install(self, args=None, build_dir=None):
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
index 1c3dfae4c..8d8c75ec8 100644
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -105,6 +105,7 @@ request_timeout = 60                  # environment CONAN_REQUEST_TIMEOUT (secon
 # non_interactive = False             # environment CONAN_NON_INTERACTIVE
 
 # conan_make_program = make           # environment CONAN_MAKE_PROGRAM (overrides the make program used in AutoToolsBuildEnvironment.make)
+# conan_cmake_program = cmake         # environment CONAN_CMAKE_PROGRAM (overrides the make program used in CMake.cmake_program)
 
 # cmake_generator                     # environment CONAN_CMAKE_GENERATOR
 # http://www.vtk.org/Wiki/CMake_Cross_Compiling
@@ -205,6 +206,7 @@ class ConanClientConfigParser(ConfigParser, object):
 
                "CONAN_BASH_PATH": self._env_c("general.bash_path", "CONAN_BASH_PATH", None),
                "CONAN_MAKE_PROGRAM": self._env_c("general.conan_make_program", "CONAN_MAKE_PROGRAM", None),
+               "CONAN_CMAKE_PROGRAM": self._env_c("general.conan_cmake_program", "CONAN_CMAKE_PROGRAM", None),
                "CONAN_TEMP_TEST_FOLDER": self._env_c("general.temp_test_folder", "CONAN_TEMP_TEST_FOLDER", "False"),
                "CONAN_SKIP_VS_PROJECTS_UPGRADE": self._env_c("general.skip_vs_projects_upgrade", "CONAN_SKIP_VS_PROJECTS_UPGRADE", "False"),
                "CONAN_HOOKS": self._env_c("hooks", "CONAN_HOOKS", None),
diff --git a/conans/client/conf/detect.py b/conans/client/conf/detect.py
index 612d8dbf3..ccc5734e2 100644
--- a/conans/client/conf/detect.py
+++ b/conans/client/conf/detect.py
@@ -5,6 +5,7 @@ from subprocess import PIPE, Popen, STDOUT
 
 from conans.client.output import Color
 from conans.client.tools.win import latest_visual_studio_version_installed
+from conans.client.tools import OSInfo
 from conans.model.version import Version
 
 
@@ -168,16 +169,11 @@ adjusting 'compiler.libcxx=libstdc++11'
 
 
 def detected_os():
-    result = platform.system()
-    if result == "Darwin":
+    if OSInfo().is_macos:
         return "Macos"
-    if result.startswith("CYGWIN"):
+    if OSInfo().is_windows:
         return "Windows"
-    if result.startswith("MINGW32_NT") or result.startswith("MINGW64_NT"):
-        return "Windows"
-    if result.startswith("MSYS_NT"):
-        return "Windows"
-    return result
+    return platform.system()
 
 
 def _detect_os_arch(result, output):
diff --git a/conans/client/rest/rest_client_common.py b/conans/client/rest/rest_client_common.py
index a6631e288..8cace86ca 100644
--- a/conans/client/rest/rest_client_common.py
+++ b/conans/client/rest/rest_client_common.py
@@ -156,7 +156,16 @@ class RestCommonMethods(object):
             response.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
             raise get_exception_from_error(response.status_code)(response.text)
 
-        result = json.loads(decode_text(response.content))
+        content = decode_text(response.content)
+        content_type = response.headers.get("Content-Type")
+        if content_type != 'application/json':
+            raise ConanException("%s\n\nResponse from remote is not json, but '%s'"
+                                 % (content, content_type))
+
+        try:  # This can fail, if some proxy returns 200 and an html message
+            result = json.loads(content)
+        except Exception:
+            raise ConanException("Remote responded with broken json: %s" % content)
         if not isinstance(result, dict):
             raise ConanException("Unexpected server response %s" % result)
         return result
diff --git a/conans/client/tools/oss.py b/conans/client/tools/oss.py
index 909025be8..2c7106e70 100644
--- a/conans/client/tools/oss.py
+++ b/conans/client/tools/oss.py
@@ -85,24 +85,21 @@ class OSInfo(object):
     """
 
     def __init__(self):
+        system = platform.system()
         self.os_version = None
         self.os_version_name = None
-        self.is_linux = platform.system() == "Linux"
+        self.is_linux = system == "Linux"
         self.linux_distro = None
-        self.is_windows = platform.system() == "Windows"
-        self.is_macos = platform.system() == "Darwin"
-        self.is_freebsd = platform.system() == "FreeBSD"
-        self.is_solaris = platform.system() == "SunOS"
+        self.is_msys = system.startswith("MING") or system.startswith("MSYS_NT")
+        self.is_cygwin = system.startswith("CYGWIN_NT")
+        self.is_windows = system == "Windows" or self.is_msys or self.is_cygwin
+        self.is_macos = system == "Darwin"
+        self.is_freebsd = system == "FreeBSD"
+        self.is_solaris = system == "SunOS"
         self.is_posix = os.pathsep == ':'
 
         if self.is_linux:
-            import distro
-            self.linux_distro = distro.id()
-            self.os_version = Version(distro.version())
-            version_name = distro.codename()
-            self.os_version_name = version_name if version_name != "n/a" else ""
-            if not self.os_version_name and self.linux_distro == "debian":
-                self.os_version_name = self.get_debian_version_name(self.os_version)
+            self._get_linux_distro_info()
         elif self.is_windows:
             self.os_version = self.get_win_os_version()
             self.os_version_name = self.get_win_version_name(self.os_version)
@@ -116,6 +113,15 @@ class OSInfo(object):
             self.os_version = Version(platform.release())
             self.os_version_name = self.get_solaris_version_name(self.os_version)
 
+    def _get_linux_distro_info(self):
+        import distro
+        self.linux_distro = distro.id()
+        self.os_version = Version(distro.version())
+        version_name = distro.codename()
+        self.os_version_name = version_name if version_name != "n/a" else ""
+        if not self.os_version_name and self.linux_distro == "debian":
+            self.os_version_name = self.get_debian_version_name(self.os_version)
+
     @property
     def with_apt(self):
         return self.is_linux and self.linux_distro in \
@@ -143,7 +149,7 @@ class OSInfo(object):
         if "opensuse" in self.linux_distro or "sles" in self.linux_distro:
             return True
         return False
-    
+
     @staticmethod
     def get_win_os_version():
         """
@@ -264,7 +270,7 @@ class OSInfo(object):
     @staticmethod
     def uname(options=None):
         options = " %s" % options if options else ""
-        if platform.system() != "Windows":
+        if not OSInfo().is_windows:
             raise ConanException("Command only for Windows operating system")
         custom_bash_path = OSInfo.bash_path()
         if not custom_bash_path:
@@ -291,7 +297,7 @@ class OSInfo(object):
         if "cygwin" in output:
             return CYGWIN
         elif "msys" in output or "mingw" in output:
-            output = OSInfo.uname("-or")
+            output = OSInfo.uname("-r")
             if output.startswith("2"):
                 return MSYS2
             elif output.startswith("1"):
@@ -305,7 +311,6 @@ class OSInfo(object):
 
 
 def cross_building(settings, self_os=None, self_arch=None):
-
     ret = get_cross_building_settings(settings, self_os, self_arch)
     build_os, build_arch, host_os, host_arch = ret
 
diff --git a/conans/server/rest/multipart_encoder.py b/conans/server/rest/multipart_encoder.py
index c2d3e0f8f..6a2dbdc76 100644
--- a/conans/server/rest/multipart_encoder.py
+++ b/conans/server/rest/multipart_encoder.py
@@ -77,7 +77,7 @@ def read_in_chunks(file_object, chunk_size=1024):
 #             yield buf1
 #         else:
         yield buf1
-        #yield buf2
+        # yield buf2
 
 
 if __name__ == "__main__":
diff --git a/conans/server/rest/server.py b/conans/server/rest/server.py
index 2819202c4..5e726df32 100644
--- a/conans/server/rest/server.py
+++ b/conans/server/rest/server.py
@@ -1,6 +1,5 @@
 import bottle
 
-from conans.model.version import Version
 from conans.server.rest.api_v1 import ApiV1
 from conans.server.rest.api_v2 import ApiV2
 
diff --git a/conans/server/service/mime.py b/conans/server/service/mime.py
index 0e7a4ee89..4a503fb41 100644
--- a/conans/server/service/mime.py
+++ b/conans/server/service/mime.py
@@ -6,4 +6,4 @@ def get_mime_type(filepath):
     else:
         mimetype = "auto"
 
-    return mimetype
\ No newline at end of file
+    return mimetype


Test patch:
diff --git a/conans/test/functional/command/remote_verify_ssl_test.py b/conans/test/functional/command/remote_verify_ssl_test.py
index 0ce3500be..f6f842408 100644
--- a/conans/test/functional/command/remote_verify_ssl_test.py
+++ b/conans/test/functional/command/remote_verify_ssl_test.py
@@ -7,6 +7,7 @@ from conans.test.utils.tools import TestClient
 resp = Response()
 resp._content = b'{"results": []}'
 resp.status_code = 200
+resp.headers = {"Content-Type": "application/json"}
 
 
 class RequesterMockTrue(object):
@@ -54,4 +55,3 @@ class VerifySSLTest(unittest.TestCase):
         self.client = TestClient(requester_class=RequesterMockFalse)
         self.client.run("remote add myremote https://localhost False")
         self.client.run("search op* -r myremote")
-
diff --git a/conans/test/functional/old/download_retries_test.py b/conans/test/functional/old/download_retries_test.py
index 216ea5189..5be5feb21 100644
--- a/conans/test/functional/old/download_retries_test.py
+++ b/conans/test/functional/old/download_retries_test.py
@@ -38,7 +38,7 @@ class MyConanfile(ConanFile):
             ok = None
             status_code = None
             charset = None
-            headers = {}
+            headers = {"Content-Type": "application/json"}
 
             def __init__(self, ok, status_code):
                 self.ok = ok
diff --git a/conans/test/functional/remote/server_error_test.py b/conans/test/functional/remote/server_error_test.py
new file mode 100644
index 000000000..9ae0b47de
--- /dev/null
+++ b/conans/test/functional/remote/server_error_test.py
@@ -0,0 +1,63 @@
+import unittest
+
+
+from conans.test.utils.tools import TestClient, TestServer
+from collections import namedtuple
+
+
+class Error200NoJson(unittest.TestCase):
+
+    def test_error_no_json(self):
+        class RequesterMock(object):
+            def __init__(self, *args, **kwargs):
+                pass
+
+            def get(self, *args, **kwargs):  # @UnusedVariable
+                # Response must be binary, it is decoded in RestClientCommon
+                return namedtuple("Response", "status_code headers content")(200, {}, b'<>')
+
+        # https://github.com/conan-io/conan/issues/3432
+        client = TestClient(servers={"default": TestServer()},
+                            requester_class=RequesterMock,
+                            users={"default": [("lasote", "mypass")]})
+
+        client.run("install pkg/ref@user/testing", assert_error=True)
+        self.assertIn("ERROR: <>", client.out)
+        self.assertIn("Response from remote is not json, but 'None'", client.out)
+
+    def test_error_broken_json(self):
+        class RequesterMock(object):
+            def __init__(self, *args, **kwargs):
+                pass
+
+            def get(self, *args, **kwargs):  # @UnusedVariable
+                # Response must be binary, it is decoded in RestClientCommon
+                headers = {"Content-Type": "application/json"}
+                return namedtuple("Response", "status_code headers content")(200, headers, b'<>')
+
+        # https://github.com/conan-io/conan/issues/3432
+        client = TestClient(servers={"default": TestServer()},
+                            requester_class=RequesterMock,
+                            users={"default": [("lasote", "mypass")]})
+
+        client.run("install pkg/ref@user/testing", assert_error=True)
+        self.assertIn("Remote responded with broken json: <>", client.out)
+
+    def test_error_json(self):
+        class RequesterMock(object):
+            def __init__(self, *args, **kwargs):
+                pass
+
+            def get(self, *args, **kwargs):  # @UnusedVariable
+                # Response must be binary, it is decoded in RestClientCommon
+                headers = {"Content-Type": "application/json"}
+                return namedtuple("Response", "status_code headers content")(200, headers,
+                                                                             b'[1, 2, 3]')
+
+        # https://github.com/conan-io/conan/issues/3432
+        client = TestClient(servers={"default": TestServer()},
+                            requester_class=RequesterMock,
+                            users={"default": [("lasote", "mypass")]})
+
+        client.run("install pkg/ref@user/testing", assert_error=True)
+        self.assertIn("ERROR: Unexpected server response [1, 2, 3]", client.out)
diff --git a/conans/test/unittests/client/build/cmake_test.py b/conans/test/unittests/client/build/cmake_test.py
index 5bd377007..635607dcb 100644
--- a/conans/test/unittests/client/build/cmake_test.py
+++ b/conans/test/unittests/client/build/cmake_test.py
@@ -1161,6 +1161,32 @@ build_type: [ Release]
             cmake.build()
             self.assertTrue(vcvars_mock.called, "vcvars weren't called")
 
+    def test_cmake_program(self):
+        conanfile = ConanFileMock()
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "14"
+        conanfile.settings = settings
+
+        cmake = CMake(conanfile, parallel=False)
+        cmake.build()
+        self.assertEqual("cmake --build %s" % CMakeTest.scape("."), conanfile.command)
+
+        cmake = CMake(conanfile, cmake_program="use_another_cmake", parallel=False)
+        cmake.build()
+        self.assertEqual("use_another_cmake --build %s" % CMakeTest.scape("."), conanfile.command)
+
+        with tools.environment_append({"CONAN_CMAKE_PROGRAM": "my_custom_cmake"}):
+            cmake = CMake(conanfile, parallel=False)
+            cmake.build()
+            self.assertEqual("my_custom_cmake --build %s"  % CMakeTest.scape("."), conanfile.command)
+
+        with tools.environment_append({"CONAN_CMAKE_PROGRAM": "cmake_from_environment_has_priority"}):
+            cmake = CMake(conanfile, cmake_program="but_not_cmake_from_the_ctor", parallel=False)
+            cmake.build()
+            self.assertEqual("cmake_from_environment_has_priority --build %s" % CMakeTest.scape("."), conanfile.command)
+
     def test_msbuild_verbosity(self):
         settings = Settings.loads(default_settings_yml)
         settings.os = "Windows"
@@ -1204,3 +1230,4 @@ build_type: [ Release]
             cmake.test(output_on_failure=True)
             self.assertEquals(conanfile.captured_env["CTEST_OUTPUT_ON_FAILURE"], "1")
             self.assertEquals(conanfile.captured_env["CTEST_PARALLEL_LEVEL"], "666")
+
diff --git a/conans/test/unittests/client/conf/detect/__init__.py b/conans/test/unittests/client/conf/detect/__init__.py
new file mode 100644
index 000000000..992ec4875
--- /dev/null
+++ b/conans/test/unittests/client/conf/detect/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/conans/test/unittests/client/conf/detect/detected_os_test.py b/conans/test/unittests/client/conf/detect/detected_os_test.py
new file mode 100644
index 000000000..e194f7cfb
--- /dev/null
+++ b/conans/test/unittests/client/conf/detect/detected_os_test.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import unittest
+from mock import mock
+from conans.client.conf.detect import detected_os
+from conans.client.tools import OSInfo
+
+
+class DetectedOSTest(unittest.TestCase):
+    def test_windows(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Windows')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_cygwin(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='CYGWIN_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_msys(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MSYS_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_mingw32(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW32_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_mingw64(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW64_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_darwin(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Darwin')):
+            self.assertEqual(detected_os(), "Macos")
+
+    def test_linux(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Linux')):
+            with mock.patch.object(OSInfo, '_get_linux_distro_info'):
+                self.assertEqual(detected_os(), "Linux")
+
+    def test_freebsd(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='FreeBSD')):
+            self.assertEqual(detected_os(), "FreeBSD")
+
+    def test_solaris(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='SunOS')):
+            self.assertEqual(detected_os(), "SunOS")
diff --git a/conans/test/unittests/client/tools/os_info/__init__.py b/conans/test/unittests/client/tools/os_info/__init__.py
new file mode 100644
index 000000000..992ec4875
--- /dev/null
+++ b/conans/test/unittests/client/tools/os_info/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/conans/test/unittests/client/tools/os_info/osinfo_test.py b/conans/test/unittests/client/tools/os_info/osinfo_test.py
new file mode 100644
index 000000000..c49f963f7
--- /dev/null
+++ b/conans/test/unittests/client/tools/os_info/osinfo_test.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import unittest
+import platform
+import os
+from conans.client.tools import OSInfo, environment_append, CYGWIN, MSYS2, MSYS, remove_from_path
+from conans.errors import ConanException
+from mock import mock
+from nose.plugins.attrib import attr
+
+
+class OSInfoTest(unittest.TestCase):
+
+    def setUp(self):
+        self._uname = None
+        self._version = None
+
+    def subprocess_check_output_mock(self, cmd, shell):
+        if cmd.endswith('"uname"'):
+            return self._uname.encode()
+        elif cmd.endswith('"uname -or"'):
+            return self._version.encode()
+        raise ValueError("don't know how to respond to %s" % cmd)
+
+    def test_windows(self):
+        self._uname = None
+        self._version = None
+        with mock.patch("platform.system", mock.MagicMock(return_value='Windows')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    with remove_from_path("uname"):
+                        with remove_from_path("bash"):
+                            # FIXME: bug in environment_append removing variables which aren't exist (another PR)
+                            new_env = dict()
+                            if 'CONAN_BASH_PATH' in os.environ:
+                                new_env['CONAN_BASH_PATH'] = None
+                            with environment_append(new_env):
+                                self.assertTrue(OSInfo().is_windows)
+                                self.assertFalse(OSInfo().is_cygwin)
+                                self.assertFalse(OSInfo().is_msys)
+                                self.assertFalse(OSInfo().is_linux)
+                                self.assertFalse(OSInfo().is_freebsd)
+                                self.assertFalse(OSInfo().is_macos)
+                                self.assertFalse(OSInfo().is_solaris)
+
+                                with self.assertRaises(ConanException):
+                                    OSInfo.uname()
+                                self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_cygwin(self):
+        self._uname = 'CYGWIN_NT-10.0'
+        self._version = '2.11.2(0.329/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertTrue(OSInfo().is_cygwin)
+                    self.assertFalse(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), CYGWIN)
+
+    def test_msys2(self):
+        self._uname = 'MSYS_NT-10.0'
+        self._version = '1.0.18(0.48/3/2)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS)
+
+    def test_mingw32(self):
+        self._uname = 'MINGW32_NT-10.0'
+        self._version = '2.10.0(0.325/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
+
+    def test_mingw64(self):
+        self._uname = 'MINGW64_NT-10.0'
+        self._version = '2.4.0(0.292/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
+
+    def test_linux(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Linux')):
+            with mock.patch.object(OSInfo, '_get_linux_distro_info'):
+                self.assertFalse(OSInfo().is_windows)
+                self.assertFalse(OSInfo().is_cygwin)
+                self.assertFalse(OSInfo().is_msys)
+                self.assertTrue(OSInfo().is_linux)
+                self.assertFalse(OSInfo().is_freebsd)
+                self.assertFalse(OSInfo().is_macos)
+                self.assertFalse(OSInfo().is_solaris)
+
+                with self.assertRaises(ConanException):
+                    OSInfo.uname()
+                self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_macos(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Darwin')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertFalse(OSInfo().is_freebsd)
+            self.assertTrue(OSInfo().is_macos)
+            self.assertFalse(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_freebsd(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='FreeBSD')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertTrue(OSInfo().is_freebsd)
+            self.assertFalse(OSInfo().is_macos)
+            self.assertFalse(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_solaris(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='SunOS')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertFalse(OSInfo().is_freebsd)
+            self.assertFalse(OSInfo().is_macos)
+            self.assertTrue(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())