Repository: conan-io/conan

Issue:
Hard to debug error ERROR: Expecting value: line 1 column 1 (char 0)
Hi, first of all i want to congratulate you guys on this great project that is conan.

I performed some conan copy to rename the owner of the package on custom packages that some have sources other get the sources from git and so on, they all are different but now i'm getting this anoying error when i try to do a conan install on some packages:
```
ERROR: Expecting value: line 1 column 1 (char 0)
```
The problem is that it is very hard to know what package is creating this error.

I kind understand that the problem was that the conan copy does not copy sources or it seems... something like that i can't really explain, but doing the conan create again on the package seems to fix this... (when i find the package with error... but i got many and is hard to find the one in error or ones)

It will be nice to add the package name on the error to easily find the package that is creating the problem (many package dependencies create this hard to find package in error)

Discussion:

Patch:
diff --git a/conans/client/build/msbuild.py b/conans/client/build/msbuild.py
index a67c0146b..394c9cc90 100644
--- a/conans/client/build/msbuild.py
+++ b/conans/client/build/msbuild.py
@@ -30,14 +30,48 @@ class MSBuild(object):
     def build(self, project_file, targets=None, upgrade_project=True, build_type=None, arch=None,
               parallel=True, force_vcvars=False, toolset=None, platforms=None, use_env=True,
               vcvars_ver=None, winsdk_version=None, properties=None, output_binary_log=None,
-              property_file_name=None, verbosity=None):
+              property_file_name=None, verbosity=None, definitions=None):
+        """
+        :param project_file: Path to the .sln file.
+        :param targets: List of targets to build.
+        :param upgrade_project: Will call devenv to upgrade the solution to your current Visual Studio.
+        :param build_type: Use a custom build type name instead of the default settings.build_type one.
+        :param arch: Use a custom architecture name instead of the settings.arch one.
+        It will be used to build the /p:Configuration= parameter of MSBuild.
+        It can be used as the key of the platforms parameter. E.g. arch="x86", platforms={"x86": "i386"}
+        :param parallel: Will use the configured number of cores in the conan.conf file or tools.cpu_count():
+        In the solution: Building the solution with the projects in parallel. (/m: parameter).
+        CL compiler: Building the sources in parallel. (/MP: compiler flag)
+        :param force_vcvars: Will ignore if the environment is already set for a different Visual Studio version.
+        :param toolset: Specify a toolset. Will append a /p:PlatformToolset option.
+        :param platforms: Dictionary with the mapping of archs/platforms from Conan naming to another one.
+        It is useful for Visual Studio solutions that have a different naming in architectures.
+        Example: platforms={"x86":"Win32"} (Visual solution uses "Win32" instead of "x86").
+        This dictionary will update the default one:
+        msvc_arch = {'x86': 'x86', 'x86_64': 'x64', 'armv7': 'ARM', 'armv8': 'ARM64'}
+        :param use_env: Applies the argument /p:UseEnv=true to the MSBuild call.
+        :param vcvars_ver: Specifies the Visual Studio compiler toolset to use.
+        :param winsdk_version: Specifies the version of the Windows SDK to use.
+        :param properties: Dictionary with new properties, for each element in the dictionary {name: value}
+        it will append a /p:name="value" option.
+        :param output_binary_log: If set to True then MSBuild will output a binary log file called msbuild.binlog in
+        the working directory. It can also be used to set the name of log file like this
+        output_binary_log="my_log.binlog".
+        This parameter is only supported starting from MSBuild version 15.3 and onwards.
+        :param property_file_name: When None it will generate a file named conan_build.props.
+        You can specify a different name for the generated properties file.
+        :param verbosity: Specifies verbosity level (/verbosity: parameter)
+        :param definitions: Dictionary with additional compiler definitions to be applied during the build.
+        Use value of None to set compiler definition with no value.
+        :return: status code of the MSBuild command invocation
+        """
 
         property_file_name = property_file_name or "conan_build.props"
         self.build_env.parallel = parallel
 
         with tools.environment_append(self.build_env.vars):
             # Path for custom properties file
-            props_file_contents = self._get_props_file_contents()
+            props_file_contents = self._get_props_file_contents(definitions)
             save(property_file_name, props_file_contents)
             vcvars = vcvars_command(self._conanfile.settings, force=force_vcvars,
                                     vcvars_ver=vcvars_ver, winsdk_version=winsdk_version)
@@ -129,7 +163,11 @@ class MSBuild(object):
 
         return " ".join(command)
 
-    def _get_props_file_contents(self):
+    def _get_props_file_contents(self, definitions=None):
+
+        def format_macro(name, value):
+            return "%s=%s" % (name, value) if value else name
+
         # how to specify runtime in command line:
         # https://stackoverflow.com/questions/38840332/msbuild-overrides-properties-while-building-vc-project
         runtime_library = {"MT": "MultiThreaded",
@@ -145,23 +183,31 @@ class MSBuild(object):
             flags = vs_build_type_flags(self._settings, with_flags=False)
             flags.append(vs_std_cpp(self._settings))
 
-        flags_str = " ".join(list(filter(None, flags))) # Removes empty and None elements
+        if definitions:
+            definitions = ";".join([format_macro(name, definitions[name]) for name in definitions])
+
+        flags_str = " ".join(list(filter(None, flags)))  # Removes empty and None elements
         additional_node = "<AdditionalOptions>" \
                           "{} %(AdditionalOptions)" \
                           "</AdditionalOptions>".format(flags_str) if flags_str else ""
         runtime_node = "<RuntimeLibrary>" \
                        "{}" \
                        "</RuntimeLibrary>".format(runtime_library) if runtime_library else ""
+        definitions_node = "<PreprocessorDefinitions>" \
+                           "{};%(PreprocessorDefinitions)" \
+                           "</PreprocessorDefinitions>".format(definitions) if definitions else ""
         template = """<?xml version="1.0" encoding="utf-8"?>
 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemDefinitionGroup>
     <ClCompile>
       {runtime_node}
       {additional_node}
+      {definitions_node}
     </ClCompile>
   </ItemDefinitionGroup>
 </Project>""".format(**{"runtime_node": runtime_node,
-                        "additional_node": additional_node})
+                        "additional_node": additional_node,
+                        "definitions_node": definitions_node})
         return template
 
     @staticmethod
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/generators/cmake_common.py b/conans/client/generators/cmake_common.py
index 1f31b9ced..01d58a36b 100644
--- a/conans/client/generators/cmake_common.py
+++ b/conans/client/generators/cmake_common.py
@@ -397,6 +397,10 @@ function(check_compiler_version)
 endfunction()
 
 function(conan_check_compiler)
+    if(CONAN_DISABLE_CHECK_COMPILER)
+        conan_message(STATUS "WARN: Disabled conan compiler checks")
+        return()
+    endif()
     if(NOT DEFINED CMAKE_CXX_COMPILER_ID)
         if(DEFINED CMAKE_C_COMPILER_ID)
             conan_message(STATUS "This project seems to be plain C, using '${CMAKE_C_COMPILER_ID}' compiler")
@@ -406,10 +410,6 @@ function(conan_check_compiler)
             message(FATAL_ERROR "This project seems to be plain C, but no compiler defined")
         endif()
     endif()
-    if(CONAN_DISABLE_CHECK_COMPILER)
-        conan_message(STATUS "WARN: Disabled conan compiler checks")
-        return()
-    endif()
     if(NOT CMAKE_CXX_COMPILER_ID AND NOT CMAKE_C_COMPILER_ID)
         # This use case happens when compiler is not identified by CMake, but the compilers are there and work
         conan_message(STATUS "*** WARN: CMake was not able to identify a C or C++ compiler ***")
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/client/tools/win.py b/conans/client/tools/win.py
index 3ecb9cb2b..7ecb7a7b1 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -120,7 +120,7 @@ def msvc_build_command(settings, sln_path, targets=None, upgrade_project=True, b
 @deprecation.deprecated(deprecated_in="1.2", removed_in="2.0",
                         details="Use the MSBuild() build helper instead")
 def build_sln_command(settings, sln_path, targets=None, upgrade_project=True, build_type=None,
-                      arch=None, parallel=True, toolset=None, platforms=None, output=None, verbosity=None):
+                      arch=None, parallel=True, toolset=None, platforms=None, output=None, verbosity=None, definitions=None):
     """
     Use example:
         build_command = build_sln_command(self.settings, "myfile.sln", targets=["SDL2_image"])
@@ -133,7 +133,7 @@ def build_sln_command(settings, sln_path, targets=None, upgrade_project=True, bu
     tmp._output = output
 
     # Generate the properties file
-    props_file_contents = tmp._get_props_file_contents()
+    props_file_contents = tmp._get_props_file_contents(definitions)
     tmp_path = os.path.join(mkdir_tmp(), ".conan_properties")
     save(tmp_path, props_file_contents)
 
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/generators/cmake_test.py b/conans/test/functional/generators/cmake_test.py
index 7d8c5cfdc..1e19c5c37 100644
--- a/conans/test/functional/generators/cmake_test.py
+++ b/conans/test/functional/generators/cmake_test.py
@@ -9,6 +9,32 @@ from conans.test.utils.tools import TestClient
 
 class CMakeGeneratorTest(unittest.TestCase):
 
+    def test_no_check_compiler(self):
+        # https://github.com/conan-io/conan/issues/4268
+        file_content = '''from conans import ConanFile, CMake
+
+class ConanFileToolsTest(ConanFile):
+    generators = "cmake"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+    '''
+
+        cmakelists = '''cmake_minimum_required(VERSION 2.8)
+PROJECT(conanzlib LANGUAGES NONE)
+set(CONAN_DISABLE_CHECK_COMPILER TRUE)
+
+include(conanbuildinfo.cmake)
+CONAN_BASIC_SETUP()
+'''
+        client = TestClient()
+        client.save({"conanfile.py": file_content,
+                     "CMakeLists.txt": cmakelists})
+
+        client.run('install .')
+        client.run('build .')
+
     @attr('slow')
     def no_output_test(self):
         client = TestClient()
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/old/libcxx_setting_test.py b/conans/test/functional/old/libcxx_setting_test.py
index ed8ebc4a0..20f020379 100644
--- a/conans/test/functional/old/libcxx_setting_test.py
+++ b/conans/test/functional/old/libcxx_setting_test.py
@@ -39,15 +39,9 @@ endforeach()
 '''
 
 
-def nowintest(func):
-    if platform.system() == "Windows":
-        func.__test__ = False
-    return func
-
-
 class LibcxxSettingTest(unittest.TestCase):
 
-    @nowintest
+    @unittest.skipIf(platform.system() == "Windows", "Not in Windows")
     def test_declared_stdlib_and_passed(self):
         client = TestClient()
         client.save({"conanfile.py": file_content,
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/msbuild_test.py b/conans/test/unittests/client/build/msbuild_test.py
index 666e562bf..126139866 100644
--- a/conans/test/unittests/client/build/msbuild_test.py
+++ b/conans/test/unittests/client/build/msbuild_test.py
@@ -146,6 +146,32 @@ class MSBuildTest(unittest.TestCase):
         self.assertIn('/p:MyProp1="MyValue1"', command)
         self.assertIn('/p:MyProp2="MyValue2"', command)
 
+    def definitions_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64",
+                                 "compiler.runtime": "MDd"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        template = msbuild._get_props_file_contents(definitions={'_WIN32_WINNT': "0x0501"})
+
+        self.assertIn("<PreprocessorDefinitions>"
+                      "_WIN32_WINNT=0x0501;"
+                      "%(PreprocessorDefinitions)</PreprocessorDefinitions>", template)
+
+    def definitions_no_value_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64",
+                                 "compiler.runtime": "MDd"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        template = msbuild._get_props_file_contents(definitions={'_DEBUG': None})
+
+        self.assertIn("<PreprocessorDefinitions>"
+                      "_DEBUG;"
+                      "%(PreprocessorDefinitions)</PreprocessorDefinitions>", template)
+
     def verbosity_default_test(self):
         settings = MockSettings({"build_type": "Debug",
                                  "compiler": "Visual Studio",
@@ -173,3 +199,4 @@ class MSBuildTest(unittest.TestCase):
         msbuild = MSBuild(conanfile)
         command = msbuild.get_command("projecshould_flags_testt_file.sln", verbosity="quiet")
         self.assertIn('/verbosity:quiet', command)
+
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())