Skip to content

Commit

Permalink
Merge pull request #200 from Cal-CS-61A-Staff/hidden-tests
Browse files Browse the repository at this point in the history
Improve client locking
  • Loading branch information
soumyabasu committed Sep 21, 2014
2 parents 2031f1f + 944aa56 commit b737213
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 50 deletions.
7 changes: 2 additions & 5 deletions client/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
import sys
sys.path.append(os.getcwd())

from client import ok

def main():
ok.ok_main(ok.parse_input())
from client.cli import ok

if __name__ == '__main__':
main()
ok.main()

Empty file added client/cli/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions client/cli/lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from client.models import *
from client.protocols import unlock
from client.utils import loading
from client.utils import output
from client import config
import argparse
import sys

def parse_input():
"""Parses command line input."""
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-t', '--tests', type=str, default='tests',
help="Path to a specific directory of tests")
return parser.parse_args()

def main():
"""Run the LockingProtocol."""
args = parse_input()
args.lock = True
cases = {case.type: case for case in core.get_testcases(config.cases)}
assignment = loading.load_tests(args.tests, cases)

logger = sys.stdout = output.OutputLogger()

protocol = unlock.LockProtocol(args, assignment, logger)
protocol.on_start()

loading.dump_tests(args.tests, assignment)

if __name__ == '__main__':
main()
15 changes: 11 additions & 4 deletions client/ok.py → client/cli/ok.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Otherwise, use -t to specify a test file manually.
"""

VERSION = '1.0.6'
VERSION = '1.0.8'

# TODO(denero) Add mechanism for removing DEVELOPER INSTRUCTIONS.
DEVELOPER_INSTRUCTIONS = """
Expand Down Expand Up @@ -144,15 +144,22 @@ def parse_input():
help="disable any network activity")
parser.add_argument('--timeout', type=int, default=10,
help="set the timeout duration for running tests")
parser.add_argument('--version', action='store_true',
help="Prints the version number and quits")
return parser.parse_args()


def server_timer():
"""Timeout for the server."""
time.sleep(0.8)

def ok_main(args):
def main():
"""Run all relevant aspects of ok.py."""
args = parse_input()

if args.version:
print("okpy=={}".format(VERSION))
exit(0)

server_thread, timer_thread = None, None
try:
print("You are running version {0} of ok.py".format(VERSION))
Expand Down Expand Up @@ -208,4 +215,4 @@ def ok_main(args):
server_thread.terminate()

if __name__ == '__main__':
ok_main(parse_input())
main()
19 changes: 15 additions & 4 deletions client/publish.py → client/cli/publish.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
"""

import os
import sys
OK_ROOT = os.path.dirname(os.path.relpath(__file__))
OK_ROOT = os.path.normpath(os.path.join(
os.path.dirname(os.path.relpath(__file__)), '..')) # Parent of cli/

from client.models import *
from client.protocols import *
import argparse
import importlib
import shutil
import sys
import zipfile
import importlib

STAGING_DIR = os.path.join(os.getcwd(), 'staging')
OK_NAME = 'ok'
Expand All @@ -24,19 +25,29 @@
REQUIRED_FILES = [
'__init__',
'exceptions',
'ok',
]
REQUIRED_FOLDERS = [
'sanction',
'utils',
]
COMMAND_LINE = [
'ok',
]

def populate_staging(staging_dir, config_path):
"""Populates the staging directory with files for ok.py."""
# Command line tools.
os.mkdir(os.path.join(staging_dir, 'cli'))
for filename in ['__init__'] + COMMAND_LINE:
filename += '.py'
fullname = os.path.join(OK_ROOT, 'cli', filename)
shutil.copy(fullname, os.path.join(staging_dir, 'cli'))
# Top-level files.
for filename in REQUIRED_FILES:
filename += '.py'
fullname = os.path.join(OK_ROOT, filename)
shutil.copyfile(fullname, os.path.join(staging_dir, filename))
# Configuration file.
shutil.copyfile(config_path, os.path.join(staging_dir, CONFIG_NAME))

for folder in REQUIRED_FOLDERS:
Expand Down
1 change: 1 addition & 0 deletions client/models/concept_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ConceptCase(grading.GradedTestCase, unlock.UnlockTestCase):
'locked': serialize.BOOL_FALSE,
'choices': serialize.SerializeArray(serialize.STR),
'never_lock': serialize.BOOL_FALSE,
'hidden': serialize.BOOL_FALSE,
}

def __init__(self, **fields):
Expand Down
1 change: 0 additions & 1 deletion client/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class Assignment(serialize.Serializable):
OPTIONAL = {
'src_files': serialize.LIST,
'params': serialize.DICT,
'hash_key': serialize.STR,
}

def __init__(self, **fields):
Expand Down
1 change: 1 addition & 0 deletions client/models/doctest_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DoctestCase(grading.GradedTestCase, unlock.UnlockTestCase):
OPTIONAL = {
'test': serialize.STR,
'locked': serialize.BOOL_FALSE,
'hidden': serialize.BOOL_FALSE,
'teardown': serialize.STR,
'never_lock': serialize.BOOL_FALSE,
}
Expand Down
30 changes: 13 additions & 17 deletions client/protocols/unlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@
HAS_READLINE = False


def normalize(x):
"""
Takes an input, removes all whitespace and converts it to lowercase.
This is so that whitespace and case sensitivity doesn't matter on inputs.
"""
return "".join(x.split())
def normalize(text):
"""Normalizes whitespace in a specified string of text."""
return " ".join(text.split())

class UnlockTestCase(core.TestCase):
"""Interface for tests that can be unlocked by the unlock protocol.
Expand All @@ -35,6 +32,7 @@ class UnlockTestCase(core.TestCase):
OPTIONAL = {
'locked': serialize.BOOL_FALSE,
'never_lock': serialize.BOOL_FALSE,
'hidden': serialize.BOOL_FALSE,
}

def on_unlock(self, logger, interact_fn):
Expand Down Expand Up @@ -68,8 +66,6 @@ def on_start(self):
if self.args.lock:
formatting.print_title('Locking tests for {}'.format(
self.assignment['name']))
if not self.assignment['hash_key']:
self.assignment['hash_key'] = self._gen_hash_key()
for test in self.assignment.tests:
lock(test, self._hash_fn)
print('Completed locking {}.'.format(self.assignment['name']))
Expand All @@ -79,18 +75,18 @@ def on_start(self):
def _alphabet(self):
return string.ascii_lowercase + string.digits

def _gen_hash_key(self):
return ''.join(random.choice(self._alphabet) for _ in range(128))

def _hash_fn(self, x):
return hmac.new(self.assignment['hash_key'].encode('utf-8'),
x.encode('utf-8')).hexdigest()
def _hash_fn(self, text):
text = normalize(text)
return hmac.new(self.assignment['name'].encode('utf-8'),
text.encode('utf-8')).hexdigest()

def lock(test, hash_fn):
print('Locking cases for Test ' + test.name)
for suite in test['suites']:
for case in suite:
if not case['never_lock'] and not case['locked']:
for case in list(suite):
if case['hidden']:
suite.remove(case)
elif not case['never_lock'] and not case['locked']:
case.on_lock(hash_fn)

######################
Expand Down Expand Up @@ -125,7 +121,7 @@ def on_interact(self):
# of unlocked test cases. This can be a useful metric
# for analytics in the future.
cases_unlocked, end_session = unlock(
test, self.logger, self.assignment['hash_key'])
test, self.logger, self.assignment['name'])
if end_session:
break
print()
Expand Down
Empty file added client/tests/cli/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion client/tests/ok_test.py → client/tests/cli/ok_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from client import ok
from client.cli import ok
import sys
import unittest

Expand Down
1 change: 0 additions & 1 deletion client/tests/models/doctest_case_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Tests the DoctestCase model."""

from client import exceptions
from client import ok
from client.models import core
from client.models import doctest_case
from client.protocols import unlock
Expand Down
22 changes: 7 additions & 15 deletions client/tests/protocols/unlock_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,37 +188,29 @@ def setUp(self):
self.test.add_suite([self.mock_case])
self.assignment.add_test(self.test)

def testWithNoHashKey(self):
def testNotYetLocked(self):
# TestCase starts as unlocked.
self.mock_case['locked'] = False
self.proto.on_start()
self.assertTrue(self.mock_case.on_lock.called)
self.mock_case.on_lock.assert_called_with(self.proto._hash_fn)
self.assertNotEqual('', self.assignment['hash_key'])

def testWithHashKey(self):
# TestCase starts as unlocked.
self.mock_case['locked'] = False
hash_key = self.proto._gen_hash_key()
self.assignment['hash_key'] = hash_key
self.proto.on_start()
self.assertTrue(self.mock_case.on_lock.called)
self.mock_case.on_lock.assert_called_with(self.proto._hash_fn)
self.assertEqual(hash_key, self.assignment['hash_key'])

def testAlreadyLocked(self):
self.mock_case['locked'] = True
hash_key = self.proto._gen_hash_key()
self.assignment['hash_key'] = hash_key
self.proto.on_start()
self.assertFalse(self.mock_case.on_lock.called)
self.assertEqual(hash_key, self.assignment['hash_key'])

def testNeverLock(self):
self.mock_case['never_lock'] = True
self.proto.on_start()
self.assertFalse(self.mock_case.on_lock.called)

def testHiddenTest(self):
self.mock_case['hidden'] = True
self.proto.on_start()
self.assertFalse(self.mock_case.on_lock.called)
self.assertEqual(0, self.test.num_cases)

class MockUnlockCase(unlock.UnlockTestCase):
def on_onlock(self, logger, interact_fn):
pass
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
# install_requires=[],
entry_points={
'console_scripts': [
'ok=client.__main__:main',
'ok-publish=client.publish:main',
'ok=client.cli.ok:main',
'ok-publish=client.cli.publish:main',
'ok-lock=client.cli.lock:main',
],
},
classifiers=[
Expand Down

0 comments on commit b737213

Please sign in to comment.