Skip to content

Instantly share code, notes, and snippets.

@johnliu
Created January 24, 2013 03:50
Show Gist options
  • Save johnliu/4617491 to your computer and use it in GitHub Desktop.
Save johnliu/4617491 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
import inspect
import os
import subprocess as sp
import sys
"""
A simple testing framework for this shell.
To run, call `python test.py` or `./test.py` after changing the permissions of this file to
executable.
"""
def expect(value_a, value_b, message):
"""
Checks if value_a is equal to value_b, if not, display the message.
"""
if value_a != value_b:
sys.stdout.write('\033[31mFAILURE\033[0m ')
sys.stdout.write(inspect.stack()[1][3] + ': ')
if message:
print '%s, expected "%s" to be "%s"' % (message, value_a, value_b)
else:
print 'expected "%s" to be "%s"' % (value_a, value_b)
else:
sys.stdout.write('\033[32mSUCCESS\033[0m ')
sys.stdout.write(inspect.stack()[1][3] + ': ')
if message:
print '%s' % message
class Test:
"""
The Test class, containing all tests for the program.
"""
SHELL_PROMPT = 'ece353sh$ '
def setup_once(self):
# Build the program first, and suppress output.
sp.call(['make', '-C', '..'], stdout=sp.PIPE)
def tear_down_once(self):
# Remove program files.
os.remove('../main.o')
os.remove('../ece353sh')
def setup(self):
# Start the process.
self.process = sp.Popen(['../ece353sh'],
stdout=sp.PIPE,
stdin=sp.PIPE,
stderr=sp.STDOUT,
shell=True)
def tear_down(self):
# Kill the process we're testing.
if self.process.poll() is None:
self.process.kill()
def test_exit(self):
# Get the output and exit code.
output = self.process.communicate(input='exit\n')[0]
exit_code = self.process.poll()
# The output should be just one shell prompt.
expect(output, self.SHELL_PROMPT, 'testing stdout stream')
# The shell should have properly exited (with status 0).
expect(exit_code, 0, 'testing exit code')
def test_exit_with_whitespace(self):
# Get the output and exit code.
output = self.process.communicate(input='\t\t\r \nexit \f\v\r\n')[0]
exit_code = self.process.poll()
# The output should be two shell prompts, since a \n (newline) was fed into the input.
expect(output, self.SHELL_PROMPT + self.SHELL_PROMPT, 'testing stdout stream')
# The shell should have properly exited (with status 0).
expect(exit_code, 0, 'testing exit code')
def test_invalid_input(self):
output = self.process.communicate(input='this_is_completely_invalid\nexit\n')[0]
# There should be two shell prompts, since we exit after typing in the invalid command.
expected = "%serror: command not found: %s\n%s" % (self.SHELL_PROMPT, "this_is_completely_invalid", self.SHELL_PROMPT)
expect(output, expected, 'testing invalid input')
def test_valid_ls(self):
output = self.process.communicate(input='/bin/ls -a .\nexit\n')[0]
# First retrieve the known output from using ls on 'sh'.
ls_output = sp.Popen(['ls', '-a', '.'], stdout=sp.PIPE).communicate()[0]
# The expected output is a shell prompt (the initial one), followed by the ls output and
# finally the next shell prompt before exiting.
expect(output,
'%s%s%s' % (self.SHELL_PROMPT, ls_output, self.SHELL_PROMPT),
'testing ls command')
def test_long_input(self):
input_str = ' ' * 600 + 'exit\n'
output = self.process.communicate(input=input_str)[0]
exit_code = self.process.poll()
# Expect an input with only one prompt.
expect(output, self.SHELL_PROMPT, 'testing long input with spaces')
# Expect the exit code to be 0.
expect(exit_code, 0, 'testing exit code')
def test_long_continuous_input(self):
# Create temporary files
long_file_name = '1' * 200
os.system('mkdir -p %(n)s/%(n)s/%(n)s/%(n)s' % {'n': long_file_name})
os.system('touch %(n)s/%(n)s/%(n)s/%(n)s/hi' % {'n': long_file_name})
# First retrieve the known output from using ls on 'sh'.
ls_output = sp.Popen(['ls', '%(n)s/%(n)s/%(n)s/%(n)s' % {'n': long_file_name}],
stdout=sp.PIPE).communicate()[0]
output = self.process.communicate(
input='/bin/ls %(n)s/%(n)s/%(n)s/%(n)s\nexit\n' % {'n': long_file_name})[0]
expect(output, '%s%s%s' % (self.SHELL_PROMPT, ls_output, self.SHELL_PROMPT),
'testing long input')
# Clean up temporary filess
os.system('rm -rf %s' % long_file_name)
def test_long_discontinuous_input(self):
# Create temporary files
long_file_names = ['1' * 200, ' ' * 400, '3' * 200]
os.system('touch %s' % long_file_names[0])
os.system('touch %s' % long_file_names[2])
os.system('echo "asdf" > %s' % (long_file_names[2]))
# First retrieve the known output from using ls on 'sh'.
cat_output = sp.Popen(['cat', long_file_names[0], long_file_names[2]],
stdout=sp.PIPE).communicate()[0]
output = self.process.communicate(
input='/bin/cat %s %s %s\nexit\n' % tuple(long_file_names))[0]
expect(output, '%s%s%s' % (self.SHELL_PROMPT, cat_output, self.SHELL_PROMPT),
'testing long input')
# Clean up temporary filess
os.system('rm %s' % long_file_names[0])
os.system('rm %s' % long_file_names[2])
def test_max_args(self):
pass
def run(obj, *args, **kwargs):
"""
Method to run all methods on a given object.
"""
# List of files to ignore.
ignore_list = ['setup', 'setup_once', 'tear_down', 'tear_down_once']
# Get all the setup functions.
setup = getattr(obj, 'setup')
tear_down = getattr(obj, 'tear_down')
setup_once = getattr(obj, 'setup_once')
tear_down_once = getattr(obj, 'tear_down_once')
# Setup for testing.
setup_once()
for name in dir(obj):
if name not in ignore_list:
attribute = getattr(obj, name)
# Run each method in the test class.
if inspect.ismethod(attribute):
setup()
attribute(*args, **kwargs)
tear_down()
# Tear down after testing.
tear_down_once()
if __name__ == '__main__':
run(Test())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment