#!/usr/bin/env python """Test how various shells parse syntax. This is only expected to work on Unix based systems. We use the unittest infrastructure, but this isn't a normal test. Usage: ref_shelex.py [options] shells... """ # Written by: Dan Christian for issue1521950 import glob import re import os, sys import optparse import subprocess import unittest TempDir = '/tmp' # where we will write temp files Shells = ['/bin/sh', '/bin/bash'] # list of shells to test against class ShellTest(unittest.TestCase): bgRe = re.compile(r'\[\d+\]\s+(\d+|\+ Done)$') # backgrounded command output def Run(self, shell, # shell to use command, # command to run filepath=None): # any files that are expected """Carefully run a shell command. Capture stdout, stderr, and exit status. Returns: (ret, out, err) ret is the return status out is the list of lines to stdout err is the list of lines to stderr """ start_cwd = os.getcwd() call = [shell, '-c', command] #print "Running: %s -c '%s'" % (shell, command) outpath = 'stdout.txt' errpath = 'stderr.txt' ret = -1 out = None err = None fileout = None try: os.chdir(TempDir) outfp = open(outpath, 'w') errfp = open(errpath, 'w') if filepath and os.path.isfile(filepath): os.remove(filepath) ret = subprocess.call(call, stdout=outfp, stderr = errfp) #print "Returned: %d" % ret outfp = open(outpath, 'r') out = outfp.readlines() os.remove(outpath) errfp = open(errpath, 'r') err = errfp.readlines() os.remove(errpath) if filepath: ffp = open(filepath) fileout = ffp.readlines() os.remove(filepath) except OSError as msg: print "Exception!", msg os.chdir(start_cwd) # leave files behind for debugging self.assertTrue(0, "Hit an exception running: " % ( ' '.join(call))) return (ret, out, err, fileout) def testTrue(self): """ Trivial case to test execution. """ for shell in Shells: cmd = '/bin/true' (ret, out, err, fout) = self.Run(shell, cmd) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals( [], out, "Expected %s -c '%s' send nothing to stdout, not: %s" % ( shell, cmd, out)) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) def testEcho(self): """ Simple case to test stdout. """ for shell in Shells: cmd = 'echo "hello world"' (ret, out, err, fout) = self.Run(shell, cmd) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals( 1, len(out), "Expected %s -c '%s' to output 1 line of stdout, not: %s" % ( shell, cmd, out)) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) def testRedirectS(self): """ output redirect with space """ for shell in Shells: fpath = "out.txt" cmd = 'echo "hi" > %s' % fpath (ret, out, err, fout) = self.Run(shell, cmd, fpath) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals( [], out, "Expected %s -c '%s' send nothing to stdout, not: %s" % ( shell, cmd, out)) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) self.assertEquals(1, len(fout)) def testRedirectNS(self): """ output redirect without space """ for shell in Shells: fpath = "out.txt" cmd = 'echo "hi"> %s' % fpath (ret, out, err, fout) = self.Run(shell, cmd, fpath) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals( [], out, "Expected %s -c '%s' send nothing to stdout, not: %s" % ( shell, cmd, out)) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) self.assertEquals(1, len(fout)) def testTwoEchoS(self): """ Two seperate output lines (with space) """ for shell in Shells: cmd = 'echo hi ; echo bye' (ret, out, err, fout) = self.Run(shell, cmd) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals(['hi\n', 'bye\n'], out) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) def testTwoEchoNS(self): """ Two seperate output lines (with space) """ for shell in Shells: cmd = 'echo hi;echo bye' (ret, out, err, fout) = self.Run(shell, cmd) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) self.assertEquals(['hi\n', 'bye\n'], out) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) def testBgEcho(self): """ Two seperate output lines but unordered """ # This is flaky. The output can vary on zsh and tcsh. Just re-run. for shell in Shells: cmd = 'echo hi&echo bye; wait' (ret, out, err, fout) = self.Run(shell, cmd) self.assertEquals( 0, ret, "Expected %s -c '%s' to return 0, not %d" % (shell, cmd, ret)) # You may get extra lines on csh (hi, bye, bg notice, done notice) self.assertTrue( len(out) in (2, 3, 4), "Expected %s -c '%s' to output 2-4 lines, not %d\n%s" % ( shell, cmd, len(out), out)) self.assertEquals( [], err, "Expected %s -c '%s' send nothing to stderr, not: %s" % ( shell, cmd, err)) def main(args): global TempDir, Shells val = os.getenv('TEMPDIR') if val: TempDir = val val = os.getenv('SHELLS') if val in ('AUTO', 'auto'): Shells = glob.glob('/bin/*sh') if not Shells: print "No shells found as /bin/*sh" sys.exit(2) elif val is not None: Shells = val.split(',') print "Testing shells: %s" % ', '.join(Shells) unittest.main() if __name__ == "__main__": main(sys.argv[1:])