diff --git a/install/travis.sh b/install/travis.sh index 6f9e37c85..08c8a86fb 100755 --- a/install/travis.sh +++ b/install/travis.sh @@ -85,7 +85,7 @@ $RUNDIR/install_cassandra.sh ############################################################################### [ -x "$(which pip)" ] || easy_install pip -pip install -U pip wheel setuptools +pip install -U pip wheel setuptools coverage pushd $REDDIT_CODE/r2 sudo python setup.py build python setup.py develop diff --git a/r2/coverage.sh b/r2/coverage.sh new file mode 100755 index 000000000..fb51a7183 --- /dev/null +++ b/r2/coverage.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e + +BASEDIR=$(readlink -f $(dirname $0)) +cd $BASEDIR + +VERSION=$(git rev-parse HEAD) +COVERDIR="$BASEDIR/build/cover-$VERSION" + +function usage() { + echo "Run unit tests and coverage reports on reddit codebase with optional" + echo "http server to the report" + echo + echo "Usage: `basename $0` [options]"; + echo + echo " -h show this message" + echo " -p \$PORT run an simple http server on \$PORT to view results" + echo " -v verbose mode (set -x)" + echo +} + +while getopts ":vhp:" opt; do + case $opt in + p) PORT="$OPTARG" ;; + v) set -x ;; + h) + usage + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done + +nosetests \ + --with-coverage \ + --cover-html \ + --cover-html-dir=$COVERDIR \ + --cover-erase \ + --cover-package=r2 + +if [ "$PORT" != "" ]; then + echo "Starting http server on :$PORT (^C to exit)" + pushd $COVERDIR + python -m SimpleHTTPServer $PORT || echo "Done." + popd +fi +rm -r $COVERDIR diff --git a/r2/r2/lib/app_globals.py b/r2/r2/lib/app_globals.py index ced9a1344..8aeacecec 100644 --- a/r2/r2/lib/app_globals.py +++ b/r2/r2/lib/app_globals.py @@ -477,7 +477,15 @@ class Globals(object): raise AttributeError("g has no attr %r" % name) def setup(self): - self.env = 'unit_test' if 'test' in sys.argv[0] else '' + self.env = '' + if ( + # handle direct invocation of "nosetests" + "test" in sys.argv[0] or + # handle "setup.py test" and all permutations thereof. + "setup.py" in sys.argv[0] and "test" in sys.argv[1:] + ): + self.env = "unit_test" + self.queues = queues.declare_queues(self) self.extension_subdomains = dict( diff --git a/r2/r2/tests/__init__.py b/r2/r2/tests/__init__.py index 106c4335e..a7f467b45 100644 --- a/r2/r2/tests/__init__.py +++ b/r2/r2/tests/__init__.py @@ -97,3 +97,10 @@ class RedditTestCase(TestCase): ".".join(current_prefix), got, want ) ) + + def autopatch(self, obj, attr, *a, **kw): + """Helper method to patch an object and automatically cleanup.""" + p = patch.object(obj, attr, *a, **kw) + m = p.start() + self.addCleanup(p.stop) + return m diff --git a/r2/r2/tests/unit/models/vote_test.py b/r2/r2/tests/unit/models/vote_test.py new file mode 100644 index 000000000..bf50d518c --- /dev/null +++ b/r2/r2/tests/unit/models/vote_test.py @@ -0,0 +1,51 @@ +from mock import patch, MagicMock +from datetime import datetime + +import pytz + +from r2.models.vote import Vote +from r2.tests import RedditTestCase + + +class TestVoteValidator(RedditTestCase): + + def setUp(self): + self.user = MagicMock(name="user") + self.thing = MagicMock(name="thing") + self.vote_data = {} + super(RedditTestCase, self).setUp() + + def cast_vote(self, **kw): + kw.setdefault("date", datetime.now(pytz.UTC)) + kw.setdefault("direction", Vote.DIRECTIONS.up) + kw.setdefault("get_previous_vote", False) + kw.setdefault("data", self.vote_data) + return Vote( + user=self.user, + thing=self.thing, + **kw + ) + + def assert_vote_effects( + self, vote, affects_score, affects_karma, + affected_thing_attr, *notes + ): + self.assertEqual(vote.effects.affects_score, affects_score) + self.assertEqual(vote.effects.affects_karma, affects_karma) + self.assertEqual(vote.affected_thing_attr, affected_thing_attr) + self.assertEqual(set(vote.effects.notes), set(notes)) + return vote + + def test_upvote_effects(self): + vote = self.cast_vote() + self.assertTrue(vote.is_upvote) + self.assertFalse(vote.is_downvote) + self.assertFalse(vote.is_self_vote) + self.assert_vote_effects(vote, True, True, "_ups") + + def test_downvote_effects(self): + vote = self.cast_vote(direction=Vote.DIRECTIONS.down) + self.assertFalse(vote.is_upvote) + self.assertTrue(vote.is_downvote) + self.assertFalse(vote.is_self_vote) + self.assert_vote_effects(vote, True, True, "_downs") diff --git a/r2/setup.py b/r2/setup.py index 741935e90..f33303790 100644 --- a/r2/setup.py +++ b/r2/setup.py @@ -85,12 +85,9 @@ setup( "webob", "webtest", ], - # Extra dependencies that aren't needed for running the app. - # * https://pythonhosted.org/setuptools/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies - # * https://github.com/pypa/sampleproject/blob/300f04dc44df51492deb859ac98ba521d2c7a17a/setup.py#L71-L77 - extras_require={ - 'test': ['mock', 'nose'], - }, + # setup tests (allowing for "python setup.py test") + tests_require=['mock', 'nose', 'coverage'], + test_suite="nose.collector", dependency_links=[ "https://github.com/reddit/snudown/archive/v1.1.3.tar.gz#egg=snudown-1.1.3", "https://s3.amazonaws.com/code.reddit.com/pycaptcha-0.4.tar.gz#egg=pycaptcha-0.4",