Repository: python-cmd2/cmd2

Issue:
Manual unit test failures on some Linux systems
I just manually ran unit tests on a Debian 9.5 system with Python 3.6 installed using conda and encountered some unit test failures in the pyscript tests.

I suspect the changes may relate to the recent StdSim PR where the new StdSim class replaced the old CopyStream class used by pyscript_bridge.py.

I do **NOT** see manual unit test failures on any of the following systems:
* macOS
* Windows
* Ubuntu 18.04
* Ubuntu 18.04 via Windows Subsystem for Linux (WSL)

So this issue is isolated to some particular versions of Linux and/or some particular versions of Python on Linux.

Discussion:

Patch:
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index c2d3eb1c..972a2be4 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -2695,9 +2695,11 @@ Usage:  Usage: unalias [-a] name [name ...]
         Non-python commands can be issued with ``pyscript_name("your command")``.
         Run python code from external script files with ``run("script.py")``
         """
-        from .pyscript_bridge import PyscriptBridge
+        from .pyscript_bridge import PyscriptBridge, CommandResult
         if self._in_py:
-            self.perror("Recursively entering interactive Python consoles is not allowed.", traceback_war=False)
+            err = "Recursively entering interactive Python consoles is not allowed."
+            self.perror(err, traceback_war=False)
+            self._last_result = CommandResult('', err)
             return False
         self._in_py = True
 


Test patch:
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 7a8a52a0..99c30af4 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -220,13 +220,13 @@ def test_base_run_pyscript(base_app, capsys, request):
     out, err = capsys.readouterr()
     assert out == expected
 
-def test_recursive_pyscript_not_allowed(base_app, capsys, request):
+def test_recursive_pyscript_not_allowed(base_app, request):
     test_dir = os.path.dirname(request.module.__file__)
     python_script = os.path.join(test_dir, 'scripts', 'recursive.py')
-    expected = 'ERROR: Recursively entering interactive Python consoles is not allowed.\n'
+    expected = 'Recursively entering interactive Python consoles is not allowed.'
 
     run_cmd(base_app, "pyscript {}".format(python_script))
-    out, err = capsys.readouterr()
+    err = base_app._last_result.stderr
     assert err == expected
 
 def test_pyscript_with_nonexist_file(base_app, capsys):
diff --git a/tests/test_pyscript.py b/tests/test_pyscript.py
index 256e63a7..d5e5a4fb 100644
--- a/tests/test_pyscript.py
+++ b/tests/test_pyscript.py
@@ -20,7 +20,7 @@ class PyscriptExample(Cmd):
         if not args.command:
             self.do_help(['media movies'])
         else:
-            print('media movies ' + str(args.__dict__))
+            self.poutput('media movies ' + str(args.__dict__))
 
     def _do_media_shows(self, args) -> None:
         if not args.command:
@@ -29,7 +29,7 @@ class PyscriptExample(Cmd):
         if not args.command:
             self.do_help(['media shows'])
         else:
-            print('media shows ' + str(args.__dict__))
+            self.poutput('media shows ' + str(args.__dict__))
 
     media_parser = argparse_completer.ACArgumentParser(prog='media')
 
@@ -84,7 +84,7 @@ class PyscriptExample(Cmd):
 
     @with_argparser(foo_parser)
     def do_foo(self, args):
-        print('foo ' + str(args.__dict__))
+        self.poutput('foo ' + str(args.__dict__))
         if self._in_py:
             FooResult = namedtuple_with_defaults('FooResult',
                                                  ['counter', 'trueval', 'constval',
@@ -110,7 +110,7 @@ class PyscriptExample(Cmd):
         out += '{'
         for key in keys:
             out += "'{}':'{}'".format(key, arg_dict[key])
-        print(out)
+        self.poutput(out)
 
 
 @pytest.fixture
@@ -126,7 +126,7 @@ class PyscriptCustomNameExample(Cmd):
         self.pyscript_name = 'custom'
 
     def do_echo(self, out):
-        print(out)
+        self.poutput(out)
 
 
 @pytest.fixture
@@ -140,7 +140,7 @@ def ps_echo():
     ('help', 'help.py'),
     ('help media', 'help_media.py'),
 ])
-def test_pyscript_help(ps_app, capsys, request, command, pyscript_file):
+def test_pyscript_help(ps_app, request, command, pyscript_file):
     test_dir = os.path.dirname(request.module.__file__)
     python_script = os.path.join(test_dir, 'pyscript', pyscript_file)
     expected = run_cmd(ps_app, command)
@@ -169,16 +169,14 @@ def test_pyscript_help(ps_app, capsys, request, command, pyscript_file):
     ('foo 11 22 33 44 55 66 -ccc', 'foo3.py'),
     ('bar 11 22', 'bar1.py'),
 ])
-def test_pyscript_out(ps_app, capsys, request, command, pyscript_file):
+def test_pyscript_out(ps_app, request, command, pyscript_file):
     test_dir = os.path.dirname(request.module.__file__)
     python_script = os.path.join(test_dir, 'pyscript', pyscript_file)
-    run_cmd(ps_app, command)
-    expected, _ = capsys.readouterr()
+    expected = run_cmd(ps_app, command)
+    assert expected
 
-    assert len(expected) > 0
-    run_cmd(ps_app, 'pyscript {}'.format(python_script))
-    out, _ = capsys.readouterr()
-    assert len(out) > 0
+    out = run_cmd(ps_app, 'pyscript {}'.format(python_script))
+    assert out
     assert out == expected
 
 
@@ -227,14 +225,12 @@ def test_pyscript_dir(ps_app, capsys, request, expected, pyscript_file):
     assert out == expected
 
 
-def test_pyscript_custom_name(ps_echo, capsys, request):
+def test_pyscript_custom_name(ps_echo, request):
     message = 'blah!'
 
     test_dir = os.path.dirname(request.module.__file__)
     python_script = os.path.join(test_dir, 'pyscript', 'custom_echo.py')
 
-    run_cmd(ps_echo, 'pyscript {}'.format(python_script))
-    expected, _ = capsys.readouterr()
-    assert len(expected) > 0
-    expected = expected.splitlines()
-    assert message == expected[0]
+    out = run_cmd(ps_echo, 'pyscript {}'.format(python_script))
+    assert out
+    assert message == out[0]