Repository: conan-io/conan

Issue:
Tool for running binaries in test()
Conan 1.1

It is typical in packages in conan center to see in test_package:

```python
    def test(self):
        with tools.environment_append(RunEnvironment(self).vars):
            bin_path = os.path.join("bin", "test_package")
            if self.settings.os == "Windows":
                self.run(bin_path)
            elif self.settings.os == "Macos":
                self.run("DYLD_LIBRARY_PATH=%s %s" % (os.environ.get('DYLD_LIBRARY_PATH', ''), bin_path))
            else:
                self.run("LD_LIBRARY_PATH=%s %s" % (os.environ.get('LD_LIBRARY_PATH', ''), bin_path))
```

As this seems very easy to automate, I'd suggest creating a tool for running test binaries with this code.




Discussion:
Is this manipulation of the `(DY)LD_LIBRARY_PATH` in the test_package actually the right way to solve the environment issue? Maybe I am completely wrong but don't we already inject the environment of the package into the build of associated test_package? Assuming that it is done, I would set up the `(DY)LD_LIBRARY_PATH` in the `package_info` method of the package and everything should work.

What am I missing?
Apple blocked the `DYLD_LIBRARY_PATH` in the environment so the only way is to specify it in the command. But about the `LD_LIBRARY_PATH` and `PATH` (for dll) should work only with the ` with tools.environment_append(RunEnvironment(self).vars):`.
 
By the way, we do not inject in the environment by default these variables, you have to do it explicitly with the `RunEnvironment`.
Thank you for the clarification regarding `RunEnvironment` and the `DYLD_LIBRARY_PATH` behavior. Since I am not an Apple user I was not aware that they treat `DYLD_LIBRARY_PATH` special.
@lasote I am thinking that the best location to implement it could be just the ``self.run()`` conanfile method? So it is easy to call any command from dependencies not only for the ``test()`` method, but for any other reason?
Yes, maybe.

Resolving patch:
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -12,6 +12,8 @@
 from conans.paths import RUN_LOG_NAME
 from conans.tools import environment_append, no_op
 from conans.client.output import Color
+from conans.client.run_environment import RunEnvironment
+from conans.client.tools.oss import os_info
 
 
 def create_options(conanfile):
@@ -255,15 +257,23 @@ def package_info(self):
         """ define cpp_build_info, flags, etc
         """
 
-    def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True):
-        if not win_bash:
-            retcode = self._runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
-        else:
+    def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True,
+            ignore_errors=False, run_environment=False):
+        def _run():
+            if not win_bash:
+                return self._runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
             # FIXME: run in windows bash is not using output
-            retcode = tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
-                                                msys_mingw=msys_mingw)
+            return tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
+                                             msys_mingw=msys_mingw)
+        if run_environment:
+            with tools.environment_append(RunEnvironment(self).vars):
+                if os_info.is_macos:
+                    command = 'DYLD_LIBRARY_PATH="%s" %s' % (os.environ.get('DYLD_LIBRARY_PATH', ''), command)
+                retcode = _run()
+        else:
+            retcode = _run()
 
-        if retcode != 0:
+        if not ignore_errors and retcode != 0:
             raise ConanException("Error %d while executing %s" % (retcode, command))
 
         return retcode


Test patch:
diff --git a/conans/test/functional/runner_test.py b/conans/test/functional/runner_test.py
--- a/conans/test/functional/runner_test.py
+++ b/conans/test/functional/runner_test.py
@@ -2,7 +2,6 @@
 import six
 import unittest
 
-from io import StringIO
 from conans.client.runner import ConanRunner
 from conans.test.utils.tools import TestClient
 
@@ -19,6 +18,18 @@ def _install_and_build(self, conanfile_text, runner=None):
         client.run("build .")
         return client
 
+    def ignore_error_test(self):
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    def source(self):
+        ret = self.run("not_a_command", ignore_errors=True)
+        self.output.info("RETCODE %s" % (ret!=0))
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("source .")
+        self.assertIn("RETCODE True", client.out)
+
     def basic_test(self):
         conanfile = '''
 from conans import ConanFile
diff --git a/conans/test/integration/run_envronment_test.py b/conans/test/integration/run_envronment_test.py
--- a/conans/test/integration/run_envronment_test.py
+++ b/conans/test/integration/run_envronment_test.py
@@ -34,3 +34,68 @@ def build(self):
         client.save({"conanfile.py": reuse}, clean_first=True)
         client.run("install . --build missing")
         client.run("build .")
+        self.assertIn("Hello Hello0", client.out)
+
+    def test_shared_run_environment(self):
+        client = TestClient()
+        cmake = """set(CMAKE_CXX_COMPILER_WORKS 1)
+set(CMAKE_CXX_ABI_COMPILED 1)
+project(MyHello CXX)
+cmake_minimum_required(VERSION 2.8.12)
+
+add_library(hello SHARED hello.cpp)
+add_executable(say_hello main.cpp)
+target_link_libraries(say_hello hello)"""
+        hello_h = """#ifdef WIN32
+  #define HELLO_EXPORT __declspec(dllexport)
+#else
+  #define HELLO_EXPORT
+#endif
+
+HELLO_EXPORT void hello();
+"""
+        hello_cpp = r"""#include "hello.h"
+#include <iostream>
+void hello(){
+    std::cout<<"Hello Tool!\n";
+}
+"""
+        main = """#include "hello.h"
+        int main(){
+            hello();
+        }
+        """
+        conanfile = """from conans import ConanFile, CMake
+class Pkg(ConanFile):
+    exports_sources = "*"
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def package(self):
+        self.copy("*say_hello.exe", dst="bin", keep_path=False)
+        self.copy("*say_hello", dst="bin", keep_path=False)
+        self.copy(pattern="*.dll", dst="bin", keep_path=False)
+        self.copy(pattern="*.dylib", dst="lib", keep_path=False)
+        self.copy(pattern="*.so", dst="lib", keep_path=False)
+"""
+        client.save({"conanfile.py": conanfile,
+                     "CMakeLists.txt": cmake,
+                     "main.cpp": main,
+                     "hello.cpp": hello_cpp,
+                     "hello.h": hello_h})
+        client.run("create . Pkg/0.1@user/testing")
+
+        reuse = '''from conans import ConanFile
+class HelloConan(ConanFile):
+    requires = "Pkg/0.1@user/testing"
+
+    def build(self):
+        self.run("say_hello", run_environment=True)
+'''
+
+        client.save({"conanfile.py": reuse}, clean_first=True)
+        client.run("install .")
+        client.run("build .")
+        self.assertIn("Hello Tool!", client.out)