Repository: ansible/ansible

Issue:
ansible-test --coverage fails to cover networking plugins
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Insert below this comment the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
ansible-connection
ansible-test

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0 (devel 872a7b4a7a) last updated 2018/03/16 13:30:39 (GMT -400)
  config file = None
  configured module search path = [u'/Users/david/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/david/code/ansible/lib/ansible
  executable location = /Users/david/code/ansible/bin/ansible
  python version = 2.7.10 (default, Jul 15 2017, 17:16:57) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

##### SUMMARY
<!--- Explain the problem briefly -->
`ansible-test network-integration --coverage` fails to properly detect code coverage for networking `cliconf` and `terminal` plugins. This error appears to be a problem with `ansible-connection` and looks like it was introduced in #36249.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

Run `git reset --hard 0feea669880e4387e7d243812fb8ba9e9d1bbb16`
Run 
```
ansible-test coverage erase
rm -rf test/results/reports/coverage/*
source hacking/env-setup
ansible-test network-integration --inventory $TESTINV vyos_smoke --coverage
ansible-test coverage html
```

Open the coverage page and search for vyos.py. The only plugin that shows up should be the vyos action plugin.

Reset to the previous commit `git reset --hard 6cbc69447b97035940131e1b8a1c68e142fd58b5` and run the same set of commands. Now the other vyos plugins should show up in the coverage results.

Adding this to the vyos plugins demonstrates the the plugins are still being executed, they just don't show up in the coverage results.
```
import q
q("hello world")
```

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```



Discussion:
Files identified in the description:
* [bin/ansible-connection](https://github.com/ansible/ansible/blob/devel/bin/ansible-connection)
* [test/runner/ansible-test](https://github.com/ansible/ansible/blob/devel/test/runner/ansible-test)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @mattclay
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
The lack of coverage is due to the change to how `ansible-connection` is invoked:

https://github.com/ansible/ansible/blob/4378542ac7ff98fba4fe2c00eec99fbab8e93d1e/lib/ansible/executor/task_executor.py#L860-L863

This bypasses the path injection used by `ansible-test` to collect code coverage.

I think the way forward for this then is to

1. Check $PATH for `ansible-connection` as before
2. If that fails, assume `dirname(argv[0])` works
3. Give up and tell the user we can't find `ansible-connection`?

Resolving patch:
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -858,9 +858,21 @@ def _start_connection(self):
         master, slave = pty.openpty()
 
         python = sys.executable
-        # Assume ansible-connection is in the same dir as sys.argv[0]
-        ansible_connection = os.path.join(os.path.dirname(sys.argv[0]), 'ansible-connection')
-        p = subprocess.Popen([python, ansible_connection, to_text(os.getppid())], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+        def find_file_in_path(filename):
+            # Check $PATH first, followed by same directory as sys.argv[0]
+            paths = os.environ['PATH'].split(os.pathsep) + [os.path.dirname(sys.argv[0])]
+            for dirname in paths:
+                fullpath = os.path.join(dirname, filename)
+                if os.path.isfile(fullpath):
+                    return fullpath
+
+            raise AnsibleError("Unable to find location of '%s'" % filename)
+
+        p = subprocess.Popen(
+            [python, find_file_in_path('ansible-connection'), to_text(os.getppid())],
+            stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+        )
         stdin = os.fdopen(master, 'wb', 0)
         os.close(slave)
 
diff --git a/lib/ansible/plugins/connection/persistent.py b/lib/ansible/plugins/connection/persistent.py
--- a/lib/ansible/plugins/connection/persistent.py
+++ b/lib/ansible/plugins/connection/persistent.py
@@ -78,9 +78,21 @@ def _start_connection(self):
         master, slave = pty.openpty()
 
         python = sys.executable
-        # Assume ansible-connection is in the same dir as sys.argv[0]
-        ansible_connection = os.path.join(os.path.dirname(sys.argv[0]), 'ansible-connection')
-        p = subprocess.Popen([python, ansible_connection, to_text(os.getppid())], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+        def find_file_in_path(filename):
+            # Check $PATH first, followed by same directory as sys.argv[0]
+            paths = os.environ['PATH'].split(os.pathsep) + [os.path.dirname(sys.argv[0])]
+            for dirname in paths:
+                fullpath = os.path.join(dirname, filename)
+                if os.path.isfile(fullpath):
+                    return fullpath
+
+            raise AnsibleError("Unable to find location of '%s'" % filename)
+
+        p = subprocess.Popen(
+            [python, find_file_in_path('ansible-connection'), to_text(os.getppid())],
+            stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+        )
         stdin = os.fdopen(master, 'wb', 0)
         os.close(slave)
 


Test patch: