Repository: avocado-framework/avocado

Issue:
External Runner-like feature for nrunner 
**Is your feature request related to a problem? Please describe.**
The legacy runner has a feature called external runner that allows users to add an executable that will actually run the test, taking the reference as an argument.

There are ways to do that with the nrunner architecture and the job API, but none that emulates the same behavior and command line arguments.

**Describe the solution you'd like**
Be able to use `--external-runner` command line and behavior with the `nrunner` architecture.

**Describe alternatives you've considered**
Not implement the same feature/command lines and let users rely on the job like like [here](https://github.com/avocado-framework/avocado/blob/2d68fe5f0d5dc3f78eaf1e5cf172b91670484d8e/examples/jobs/custom_exec_test.py#L17).

**Additional information**
The following [issue](https://github.com/avocado-framework/avocado/issues/4980) is related to this, as the external runner transform the test name.



Discussion:

Resolving patch:
diff --git a/contrib/scripts/avocado-run-inplace.py b/contrib/scripts/avocado-run-inplace.py
deleted file mode 100755
--- a/contrib/scripts/avocado-run-inplace.py
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env python3
-#
-# command line helper, wrap command line ops to avocado friendly way
-#
-# Example: avocado-run-inplace.py dnf update -y
-#
-
-import sys
-
-from avocado.core.job import Job
-from avocado.core.nrunner import Runnable
-from avocado.core.suite import TestSuite
-from avocado.utils.path import find_command
-
-
-def create_runnable_from_command(command_parts):
-    executable = find_command(command_parts[0])
-    return Runnable('exec-test',
-                    executable,
-                    *command_parts[1:])
-
-
-if __name__ == '__main__':
-    command = sys.argv[1:]
-    if not command:
-        print("ERROR: no command given", file=sys.stderr)
-        sys.exit(2)
-
-    suite = TestSuite(tests=[create_runnable_from_command(command)],
-                      name="")
-    with Job({}, [suite]) as j:
-        sys.exit(j.run())
diff --git a/scripts/external_runner.py b/scripts/external_runner.py
new file mode 100755
--- /dev/null
+++ b/scripts/external_runner.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; specifically version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# See LICENSE for more details.
+#
+# Copyright: Red Hat Inc. 2021
+# Author: Cleber Rosa <cleber@redhat.com>
+#         Beraldo Leal <bleal@redhat.com>
+
+# Command line helper, wrap command line ops to avocado friendly way. Use this
+# as the new external-runner.
+#
+# For more information, please execute:
+#
+#  $ avocado-external-runner -h
+#
+# Note: Here we are using "{uri}-{args[0]}" as the runnable identifier because
+#       it will be suitable for most of the cases. However feel free to adapt
+#       the "identifier_format" to your needs.
+
+import argparse
+import sys
+
+from avocado.core.job import Job
+from avocado.core.nrunner import Runnable
+from avocado.core.suite import TestSuite
+from avocado.utils.path import find_command
+
+
+def main():
+
+    epilog = """Examples:
+
+    $ avocado-external-runner curl redhat.com
+    $ avocado-external-runner curl "redhat.com -v" google.com
+
+ Note: If you have multiple arguments please use quotes as in
+ the example above.
+ """
+
+    parser = argparse.ArgumentParser(description='Process some integers.',
+                                     formatter_class=argparse.RawTextHelpFormatter,
+                                     epilog=epilog)
+    parser.add_argument('runner',
+                        metavar='RUNNER',
+                        type=str,
+                        help='The external runner to process the arguments.')
+    parser.add_argument('args',
+                        metavar='ARGS',
+                        type=str,
+                        nargs='+',
+                        help=('Arguments to be executed. If you have multiple '
+                              'arguments, please quote them.'))
+
+    args = parser.parse_args()
+    tests = []
+    for arg in args.args:
+        runnable = Runnable.from_args({'kind': 'exec-test',
+                                       'uri': find_command(args.runner),
+                                       'arg': arg.split()})
+        tests.append(runnable)
+
+    config = {"runner.identifier_format": "{uri}-{args[0]}"}
+    suite = TestSuite(tests=tests, name="external-runner", config=config)
+    with Job({}, [suite]) as j:
+        sys.exit(j.run())
+
+
+if __name__ == '__main__':
+    main()
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -322,6 +322,7 @@ def run(self):
                   'avocado-runner-requirement-package = avocado.core.runners.requirement_package:main',
                   'avocado-runner-sysinfo = avocado.core.runners.sysinfo:main',
                   'avocado-software-manager = avocado.utils.software_manager.main:main',
+                  'avocado-external-runner = scripts.external_runner:main',
                   ],
               "avocado.plugins.init": [
                   "xunit = avocado.plugins.xunit:XUnitInit",


Test patch: