Repository: avocado-framework/avocado

Issue:
[Software-Manager] Cannot find `sudo` in PATH but it is available.
While running `avocado-software-manager`, it complains about not finding the `sudo` command in any of the `PATH` dirs, but it is available in one of them.

```
[wrampazz@wrampazz avocado.dev]$ avocado-software-manager install vim-common
dnf module for Python is required. Using the basic support from rpm and dnf commands
Command 'sudo' could not be found in any of the PATH dirs: ['/usr/local/sbin', '/usr/bin', '/home/wrampazz/bin', '/usr/sbin', '/sbin', '/var/lib/snapd/snap/bin', '/usr/local/bin', '/usr/libexec', '/home/wrampazz/.local/bin', '/bin', '/usr/share/Modules/bin']
Parameter sudo=True provided, but sudo was not found. Please consider adding sudo to your OS image
Running '/usr/bin/dnf -y install vim-common'
Command '/usr/bin/dnf -y install vim-common' finished with 1 after 0.18606805801391602s
Failed to install vim-common

[wrampazz@wrampazz avocado.dev]$ which sudo
/usr/bin/sudo
```


Discussion:
I have not investigated deeper, but the problem may be here https://github.com/avocado-framework/avocado/blob/master/avocado/utils/path.py#L77

```
[wrampazz@wrampazz avocado-82lts]$ python
Python 3.8.5 (default, Aug 12 2020, 00:00:00) 
[GCC 10.2.1 20200723 (Red Hat 10.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from avocado.utils import path
>>> path.find_command('sudo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wrampazz/src/avocado/avocado.dev/avocado/utils/path.py", line 109, in find_command
    raise CmdNotFoundError(cmd, path_paths)
avocado.utils.path.CmdNotFoundError: Command 'sudo' could not be found in any of the PATH dirs: ['/home/wrampazz/.local/bin', '/var/lib/snapd/snap/bin', '/home/wrampazz/bin', '/usr/libexec', '/sbin', '/usr/sbin', '/bin', '/usr/share/Modules/bin', '/usr/local/bin', '/usr/bin', '/usr/local/sbin']

>>> path.find_command('echo')
'/bin/echo'
>>>
```
If your system is like mine, the following check in `find_command()` will fail for `/usr/bin/sudo`:

```python
                if not os.access(cmd_path, os.R_OK | os.X_OK):
```
> If your system is like mine, the following check in `find_command()` will fail for `/usr/bin/sudo`:
> 
> ```python
>                 if not os.access(cmd_path, os.R_OK | os.X_OK):
> ```

Yep, that is it:

```
---s--x--x.  1 root root       186592 Sep 15 12:07  sudo

>>> os.access("/usr/bin/sudo", os.R_OK | os.X_OK)
False
```

Resolving patch:
diff --git a/avocado/utils/process.py b/avocado/utils/process.py
--- a/avocado/utils/process.py
+++ b/avocado/utils/process.py
@@ -585,7 +585,7 @@ def __str__(self):
     def _prepend_sudo(cmd, shell):
         if os.getuid() != 0:
             try:
-                sudo_cmd = '%s -n' % path.find_command('sudo')
+                sudo_cmd = '%s -n' % path.find_command('sudo', check_exec=False)
             except path.CmdNotFoundError as details:
                 log.error(details)
                 log.error('Parameter sudo=True provided, but sudo was '


Test patch:
diff --git a/selftests/unit/test_utils_process.py b/selftests/unit/test_utils_process.py
--- a/selftests/unit/test_utils_process.py
+++ b/selftests/unit/test_utils_process.py
@@ -131,7 +131,7 @@ def test_is_sudo_enabled_true(self, get_owner, get_pid, _):
         get_owner.assert_called_with(process_id)
 
 
-def mock_fail_find_cmd(cmd, default=None):  # pylint: disable=W0613
+def mock_fail_find_cmd(cmd, default=None, check_exec=True):  # pylint: disable=W0613
     path_paths = ["/usr/libexec", "/usr/local/sbin", "/usr/local/bin",
                   "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
     raise path.CmdNotFoundError(cmd, path_paths)
@@ -160,7 +160,7 @@ def test_subprocess_nosudo_uid_0(self):
     def test_subprocess_sudo(self):
         expected_command = '/bin/sudo -n ls -l'
         p = process.SubProcess(cmd='ls -l', sudo=True)
-        path.find_command.assert_called_once_with('sudo')
+        path.find_command.assert_called_once_with('sudo', check_exec=False)
         self.assertEqual(p.cmd, expected_command)
 
     @unittest.mock.patch.object(path, 'find_command', mock_fail_find_cmd)
@@ -185,7 +185,7 @@ def test_subprocess_sudo_uid_0(self):
     def test_subprocess_sudo_shell(self):
         expected_command = '/bin/sudo -n -s ls -l'
         p = process.SubProcess(cmd='ls -l', sudo=True, shell=True)
-        path.find_command.assert_called_once_with('sudo')
+        path.find_command.assert_called_once_with('sudo', check_exec=False)
         self.assertEqual(p.cmd, expected_command)
 
     @unittest.mock.patch.object(path, 'find_command', mock_fail_find_cmd)
@@ -225,7 +225,7 @@ def test_run_nosudo_uid_0(self):
     def test_run_sudo(self):
         expected_command = '/bin/sudo -n ls -l'
         p = process.run(cmd='ls -l', sudo=True, ignore_status=True)
-        path.find_command.assert_called_once_with('sudo')
+        path.find_command.assert_called_once_with('sudo', check_exec=False)
         self.assertEqual(p.command, expected_command)
 
     @unittest.mock.patch.object(path, 'find_command', mock_fail_find_cmd)
@@ -250,7 +250,7 @@ def test_run_sudo_uid_0(self):
     def test_run_sudo_shell(self):
         expected_command = '/bin/sudo -n -s ls -l'
         p = process.run(cmd='ls -l', sudo=True, shell=True, ignore_status=True)
-        path.find_command.assert_called_once_with('sudo')
+        path.find_command.assert_called_once_with('sudo', check_exec=False)
         self.assertEqual(p.command, expected_command)
 
     @unittest.mock.patch.object(path, 'find_command', mock_fail_find_cmd)