Repository: scrapy/scrapy

Issue:
AttributeError from contract errback
When running a contract with a URL that returns non-200 response, I get the following:
```
2018-08-09 14:40:23 [scrapy.core.scraper] ERROR: Spider error processing <GET https://www.bureauxlocaux.com/annonce/a-louer-bureaux-a-louer-a-nantes--1289-358662> (referer: None)
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/twisted/internet/defer.py", line 653, in _runCallbacks
    current.result = callback(current.result, *args, **kw)
  File "/usr/local/lib/python3.6/site-packages/scrapy/contracts/__init__.py", line 89, in eb_wrapper
    results.addError(case, exc_info)
  File "/usr/local/lib/python3.6/unittest/runner.py", line 67, in addError
    super(TextTestResult, self).addError(test, err)
  File "/usr/local/lib/python3.6/unittest/result.py", line 17, in inner
    return method(self, *args, **kw)
  File "/usr/local/lib/python3.6/unittest/result.py", line 115, in addError
    self.errors.append((test, self._exc_info_to_string(err, test)))
  File "/usr/local/lib/python3.6/unittest/result.py", line 186, in _exc_info_to_string
    exctype, value, tb, limit=length, capture_locals=self.tb_locals)
  File "/usr/local/lib/python3.6/traceback.py", line 470, in __init__
    exc_value.__cause__.__traceback__,
AttributeError: 'getset_descriptor' object has no attribute '__traceback__'
```

Here is how `exc_info` looks like:
```
(HttpError('Ignoring non-200 response',), <class 'scrapy.spidermiddlewares.httperror.HttpError'>, <traceback object at 0x7f4bdca1d948>)
```



Discussion:
I've found the bug. It's inside `scrapy/contracts/__init__.py`.

Here's how `exc_info` is initialized:
```
exc_info = failure.value, failure.type, failure.getTracebackObject()
```

So we get `(value, type, traceback)` tuple. But it [must be](https://docs.python.org/3/library/sys.html#sys.exc_info) `(type, value, traceback)`.

I'll create a PR with a fix soon.

Resolving patch:
diff --git a/scrapy/contracts/__init__.py b/scrapy/contracts/__init__.py
--- a/scrapy/contracts/__init__.py
+++ b/scrapy/contracts/__init__.py
@@ -84,7 +84,7 @@ def cb_wrapper(response):
 
         def eb_wrapper(failure):
             case = _create_testcase(method, 'errback')
-            exc_info = failure.value, failure.type, failure.getTracebackObject()
+            exc_info = failure.type, failure.value, failure.getTracebackObject()
             results.addError(case, exc_info)
 
         request.callback = cb_wrapper


Test patch:
diff --git a/tests/test_contracts.py b/tests/test_contracts.py
--- a/tests/test_contracts.py
+++ b/tests/test_contracts.py
@@ -1,7 +1,9 @@
 from unittest import TextTestResult
 
+from twisted.python import failure
 from twisted.trial import unittest
 
+from scrapy.spidermiddlewares.httperror import HttpError
 from scrapy.spiders import Spider
 from scrapy.http import Request
 from scrapy.item import Item, Field
@@ -185,3 +187,18 @@ def test_scrapes(self):
                 self.results)
         request.callback(response)
         self.should_fail()
+
+    def test_errback(self):
+        spider = TestSpider()
+        response = ResponseMock()
+
+        try:
+            raise HttpError(response, 'Ignoring non-200 response')
+        except HttpError:
+            failure_mock = failure.Failure()
+
+        request = self.conman.from_method(spider.returns_request, self.results)
+        request.errback(failure_mock)
+
+        self.assertFalse(self.results.failures)
+        self.assertTrue(self.results.errors)