Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for running under setup.py/manage.py and travis setup. #8

Merged
merged 3 commits into from
Feb 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
build/
dist/
*.egg-info/
wheelhouse/
.eggs/
*.egg/
*.pyc
*.sw?
.coverage
test-support/django/db.sqlite3
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "pypy"
env:
- EGGS="nose==dev"
- EGGS=""
# command to install dependencies, e.g. pip install -r requirements.txt
install:
- pip='travis_retry pip'
- if [[ $TRAVIS_PYTHON_VERSION =~ '2.6' ]] ; then $pip install 'Django<1.7' ; fi
- if [[ -n "$EGGS" ]] ; then $pip install $EGGS --allow-external=nose --allow-unverified=nose ; fi
- python setup.py develop
# command to run tests, e.g. python setup.py test
script:
- python setup.py nosetests --verbosity=3
85 changes: 61 additions & 24 deletions nosepipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,16 @@ def __call__(self, result):
useshell = True

self.logger.debug("Executing %s", " ".join(argv))
popen = subprocess.Popen(argv,
cwd=self._cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=useshell,
)
try:
popen = subprocess.Popen(argv,
cwd=self._cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=useshell,
)
except OSError as e:
raise Exception("Error running %s [%s]" % (argv[0], e))

try:
stdout = popen.stdout
while True:
Expand Down Expand Up @@ -191,18 +195,49 @@ def __init__(self):
nose.plugins.Plugin.__init__(self)
self._test = None
self._test_proxy = None
self._argv = [os.path.abspath(sys.argv[0]),
'--with-process-isolation-reporter']
self._argv += ProcessIsolationPlugin._get_nose_whitelisted_argv()

# Normally, nose is called as:
# nosetests {opt1} {opt2} ...
# However, we can also be run as:
# setup.py nosetests {opt1} {opt2} ...
# When not running directly as nosetests, we need to run the
# sub-processes as `nosetests`, not `setup.py nosetests` as the output
# of setup.py interferes with the nose output. So, we need to find
# where nosetests is on the command-line and then run the
# sub-processes command-line using the args from that location.

nosetests_index = None
# Find where nosetests is in argv and start the new argv from there
# in case we're running as something like `setup.py nosetests`
for i in range(0, len(sys.argv)):
if 'nosetests' in sys.argv[i]:
self._argv = [sys.argv[i]]
nosetests_index = i
break

# If we can't find nosetests in the command-line we must be running
# from some other test runner like django's `manage.py test`. Replace
# the runner with `nosttests` and proceed...
if nosetests_index is None:
nosetests_index = 0
self._argv = ['nosetests']

self._argv += ['--with-process-isolation-reporter']
# add the rest of the args that appear in argv after `nosetests`
self._argv += ProcessIsolationPlugin._get_nose_whitelisted_argv(
offset=nosetests_index + 1)
# Getting cwd inside SubprocessTestProxy.__call__ is too late - it is
# already changed by nose
self._cwd = os.getcwd()

@staticmethod
def _get_nose_whitelisted_argv():
def _get_nose_whitelisted_argv(offset=1):
# This is the list of nose options which should be passed through to
# the launched process; boolean value defines whether the option
# takes a value or not.
#
# offset: int, the argv index of the first nosetests option.
#
whitelist = {
'--debug-log': True,
'--logging-config': True,
Expand All @@ -228,7 +263,7 @@ def _get_nose_whitelisted_argv():
'--doctest-options': True,
'--no-skip': False,
}
filtered = set(whitelist.keys()).intersection(set(sys.argv[1:]))
filtered = set(whitelist.keys()).intersection(set(sys.argv[offset:]))
result = []
for key in filtered:
result.append(key)
Expand All @@ -237,7 +272,7 @@ def _get_nose_whitelisted_argv():

# We are not finished yet: options with '=' were not handled
whitelist_keyval = [(k + "=") for k, v in whitelist.items() if v]
for arg in sys.argv[1:]:
for arg in sys.argv[offset:]:
for keyval in whitelist_keyval:
if arg.startswith(keyval):
result.append(arg)
Expand All @@ -255,18 +290,20 @@ def options(self, parser, env):
def configure(self, options, config):
self.individual = options.with_process_isolation_individual
nose.plugins.Plugin.configure(self, options, config)
if self.enabled and options.enable_plugin_coverage:
from coverage import coverage

def nothing(*args, **kwargs):
pass

# Monkey patch coverage to fix the reporting and friends
coverage.start = nothing
coverage.stop = nothing
coverage.combine = nothing
coverage.save = coverage.load

try:
if self.enabled and options.enable_plugin_coverage:
from coverage import coverage

def nothing(*args, **kwargs):
pass

# Monkey patch coverage to fix the reporting and friends
coverage.start = nothing
coverage.stop = nothing
coverage.combine = nothing
coverage.save = coverage.load
except:
pass

def do_isolate(self, test):
# XXX is there better way to access 'nosepipe_isolate'?
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
license = "BSD",
platforms = ["any"],

install_requires = ["nose>=0.1.0, ==dev"],
install_requires = ["nose>=0.1.0"],

tests_require = ["django-nose"],

url = "http://github.com/dmccombs/nosepipe/",

Expand Down
10 changes: 10 additions & 0 deletions test-support/django/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
Empty file.
52 changes: 52 additions & 0 deletions test-support/django/sample/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Django settings for nosepipe project.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'sa5ia(bu*m1j@hkiap#1yx3#vp0-(_ige!da_zk6=3*knhp1p7'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

TEMPLATE_DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = (
'django_nose',
)

MIDDLEWARE_CLASSES = ()

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}

# Use django_nose to run tests

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

# Enable nosepipe

NOSE_ARGS = ['--verbose', '--with-process-isolation']
6 changes: 6 additions & 0 deletions test-support/django/sample/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.test import TestCase


class BaseTestCase(TestCase):
def test_simple(self):
self.assertTrue(1)
58 changes: 56 additions & 2 deletions test_nosepipe.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Multiple failing tests:
... argv=["nosetests", "-v", "--with-process-isolation",
... os.path.join(directory_with_tests, "failing")],
... plugins=plugins)
... # doctest: +REPORT_NDIFF
... # doctest: +REPORT_NDIFF +ELLIPSIS
failing_tests.erroring_test ... ERROR
failing_tests.failing_test ... FAIL
<BLANKLINE>
Expand All @@ -110,4 +110,58 @@ Multiple failing tests:
----------------------------------------------------------------------
Ran 2 tests in ...s
<BLANKLINE>
FAILED (failures=1, errors=1)
FAILED (errors=1, failures=1)

Django nose:

>>> import subprocess
>>> import re

>>> # run a command and return it's output or error output
>>> def run_cmd(argv):
... try: # python 2.7+
... output = subprocess.check_output(argv, stderr=subprocess.STDOUT).decode('ascii')
... except subprocess.CalledProcessError as e:
... output = "Error running:\n{0}\nOutput:\n{1}".format(argv, e.output)
... except Exception as subprocess_e:
... try:
... useshell = False
... if sys.platform == 'win32':
... useshell = True
... popen = subprocess.Popen(argv,
... shell=useshell,
... stdout=subprocess.PIPE,
... stderr=subprocess.PIPE,
... )
... stdout, stderr = popen.communicate()
... output = "{0}{1}".format(stderr, stdout)
... except OSError as popen_e:
... output = "Error running:\n{0}\nSubprocess Output:\n{1}\nPopen Output".format(
... argv, subprocess_e, popen_e)
... return output

>>> # find all .egg directories to add to the path (were installed by setup.py develop/test)
>>> top_dir = os.getcwd()
>>> eggs = []
>>> iseggdir = re.compile('\.egg$')
>>> for top, dirs, f in os.walk(top_dir):
... for dir in dirs:
... if iseggdir.search(dir):
... eggs += [os.path.join(top, dir)]

>>> django_dir = os.path.join(directory_with_tests, "django")
>>> os.chdir(django_dir)
>>> print(run_cmd(["env",
... "PYTHONPATH={0}".format(":".join(eggs)),
... "python", "manage.py", "test", "--verbosity=1"]))
... # doctest: +REPORT_NDIFF +ELLIPSIS
.
----------------------------------------------------------------------
Ran 1 test in ...s
<BLANKLINE>
OK
nosetests --verbose --with-process-isolation --verbosity=1
Creating test database for alias 'default'...
Destroying test database for alias 'default'...
<BLANKLINE>
>>> os.chdir(top_dir)