Repository: nose-devs/nose2

Issue:
Layers is incompatible with setuptools test
Layers crashes when tests are run from setuptools via nose2.collector.collector 

```
Traceback (most recent call last):
  File "setup.py", line 8, in <module>
    test_suite="nose2.collector.collector",
  File "C:\Python34\lib\distutils\core.py", line 149, in setup
    dist.run_commands()
  File "C:\Python34\lib\distutils\dist.py", line 955, in run_commands
    self.run_command(cmd)
  File "C:\Python34\lib\distutils\dist.py", line 974, in run_command
    cmd_obj.run()
  File "c:\Projects\tdd_with_python\lib\site-packages\setuptools\command\test.py", line 146, in run
    self.with_project_on_sys_path(self.run_tests)
  File "c:\Projects\tdd_with_python\lib\site-packages\setuptools\command\test.py", line 127, in with_project_on_sys_path
    func()
  File "c:\Projects\tdd_with_python\lib\site-packages\setuptools\command\test.py", line 167, in run_tests
    testRunner=self._resolve_as_ep(self.test_runner),
  File "C:\Python34\lib\unittest\main.py", line 93, in __init__
    self.runTests()
  File "C:\Python34\lib\unittest\main.py", line 244, in runTests
    self.result = testRunner.run(self.test)
  File "C:\Python34\lib\unittest\runner.py", line 168, in run
    test(result)
  File "C:\Python34\lib\unittest\suite.py", line 87, in __call__
    return self.run(*args, **kwds)
  File "C:\Python34\lib\unittest\suite.py", line 125, in run
    test(result)
  File "C:\Python34\lib\unittest\suite.py", line 87, in __call__
    return self.run(*args, **kwds)
  File "C:\Python34\lib\unittest\suite.py", line 125, in run
    test(result)
  File "C:\Python34\lib\unittest\case.py", line 622, in __call__
    return self.run(*args, **kwds)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\collector.py", line 13, in run
    ok = self._collector(result_)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\collector.py", line 27, in _collector
    rslt = rnr.run(test)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\runner.py", line 45, in run
    self.session.hooks.startTestRun(event)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\events.py", line 224, in __call__
    result = getattr(plugin, self.method)(event)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\plugins\layers.py", line 22, in startTestRun
    event.suite = self._makeLayerSuite(event)
  File "c:\Projects\tdd_with_python\lib\site-packages\nose2\plugins\layers.py", line 26, in _makeLayerSuite
    event.suite, self.session.testLoader.suiteClass)
AttributeError: 'NoneType' object has no attribute 'suiteClass'
```

Layers uses `session.testLoader` which is set in `PluggableTestProgram.parseArgs`. This field is not set when tests are executed via the `nose2.collector.collector` entry point.

Discussion:

Patch:
diff --git a/nose2/collector.py b/nose2/collector.py
index 70fcd87..63a1854 100644
--- a/nose2/collector.py
+++ b/nose2/collector.py
@@ -13,11 +13,16 @@ def collector():
             ok = self._collector(result_)
             sys.exit(not ok)
 
-        def _collector(self, result_):
+        def _get_objects(self):
             ssn = session.Session()
             ldr = loader.PluggableTestLoader(ssn)
             rnr = runner.PluggableTestRunner(ssn)
+            return ssn, ldr, rnr
+
+        def _collector(self, result_):
+            ssn, ldr, rnr = self._get_objects()
 
+            ssn.testLoader = ldr
             ssn.loadConfigFiles('unittest.cfg', 'nose2.cfg', 'setup.cfg')
             ssn.setStartDir()
             ssn.prepareSysPath()
diff --git a/requirements-2.7.txt b/requirements-2.7.txt
index bc04b49..8a8f980 100644
--- a/requirements-2.7.txt
+++ b/requirements-2.7.txt
@@ -1,1 +1,2 @@
+mock
 -r requirements.txt
diff --git a/requirements-py26.txt b/requirements-py26.txt
index 2ed4175..e1cebc9 100644
--- a/requirements-py26.txt
+++ b/requirements-py26.txt
@@ -1,3 +1,4 @@
+mock
 unittest2==0.5.1
 --allow-external argparse
 --allow-unverified argparse


Test patch:
diff --git a/nose2/tests/unit/test_collector.py b/nose2/tests/unit/test_collector.py
index 358de17..7a24da1 100644
--- a/nose2/tests/unit/test_collector.py
+++ b/nose2/tests/unit/test_collector.py
@@ -1,3 +1,9 @@
+try:
+    from unittest import mock
+except ImportError:
+    # Older python versions dont have mock by default
+    import mock
+
 from nose2.tests._common import TestCase, RedirectStdStreams
 from nose2 import collector
 from textwrap import dedent
@@ -20,3 +26,18 @@ class TestCollector(TestCase):
         self.assertEqual("", redir.stdout.getvalue())
         self.assertTrue(re.match(r'\n-+\nRan 0 tests in \d.\d\d\ds\n\nOK\n',
                                  redir.stderr.getvalue()))
+
+    def test_collector_sets_testLoader_in_session(self):
+        """
+        session.testLoader needs to be set so that plugins that use this
+        field (like Layers) dont break.
+        """
+        test = collector.collector()
+        mock_session = mock.MagicMock()
+        mock_loader = mock.MagicMock()
+        mock_runner = mock.MagicMock()
+        test._get_objects = mock.Mock(return_value=(mock_session,
+                                                    mock_loader,
+                                                    mock_runner))
+        test._collector(None)
+        self.assertTrue(mock_session.testLoader is mock_loader)