Repository: scrapy/scrapy

Issue:
scrapy check exit code on exception
When I run `scrapy check` and a contract raises unhandled exception, the command exits with successful code `0`.

```
$ scrapy check $SPIDER_NAME
... some stuff ...
Ran 0 contracts in 0.000s
OK
$ echo $?
0
```

Is this intended, or should I fix it?


Discussion:
If that's the case, I think it is better to change it. A similar issue is going to be fixed in Scrapy 1.6 for `scrapy crawl` and `scrapy parse` commands (https://github.com/scrapy/scrapy/pull/3226/files).
@kmike is it ok if I will create a pull request with a similar change but with `scrapy check`?
@StasDeep sure, it'd be awesome if you do so!

Resolving patch:
diff --git a/scrapy/contracts/__init__.py b/scrapy/contracts/__init__.py
--- a/scrapy/contracts/__init__.py
+++ b/scrapy/contracts/__init__.py
@@ -42,7 +42,11 @@ def from_spider(self, spider, results):
         requests = []
         for method in self.tested_methods_from_spidercls(type(spider)):
             bound_method = spider.__getattribute__(method)
-            requests.append(self.from_method(bound_method, results))
+            try:
+                requests.append(self.from_method(bound_method, results))
+            except Exception:
+                case = _create_testcase(bound_method, 'contract')
+                results.addError(case, sys.exc_info())
 
         return requests
 


Test patch:
diff --git a/tests/test_contracts.py b/tests/test_contracts.py
--- a/tests/test_contracts.py
+++ b/tests/test_contracts.py
@@ -25,6 +25,21 @@ class ResponseMock(object):
     url = 'http://scrapy.org'
 
 
+class CustomSuccessContract(Contract):
+    name = 'custom_success_contract'
+
+    def adjust_request_args(self, args):
+        args['url'] = 'http://scrapy.org'
+        return args
+
+
+class CustomFailContract(Contract):
+    name = 'custom_fail_contract'
+
+    def adjust_request_args(self, args):
+        raise TypeError('Error in adjust_request_args')
+
+
 class CustomFormContract(Contract):
     name = 'custom_form'
     request_cls = FormRequest
@@ -118,12 +133,39 @@ def custom_form(self, response):
         pass
 
 
+class CustomContractSuccessSpider(Spider):
+    name = 'custom_contract_success_spider'
+
+    def parse(self, response):
+        """
+        @custom_success_contract
+        """
+        pass
+
+
+class CustomContractFailSpider(Spider):
+    name = 'custom_contract_fail_spider'
+
+    def parse(self, response):
+        """
+        @custom_fail_contract
+        """
+        pass
+
+
 class InheritsTestSpider(TestSpider):
     name = 'inherits_demo_spider'
 
 
 class ContractsManagerTest(unittest.TestCase):
-    contracts = [UrlContract, ReturnsContract, ScrapesContract, CustomFormContract]
+    contracts = [
+        UrlContract,
+        ReturnsContract,
+        ScrapesContract,
+        CustomFormContract,
+        CustomSuccessContract,
+        CustomFailContract,
+    ]
 
     def setUp(self):
         self.conman = ContractsManager(self.contracts)
@@ -137,6 +179,9 @@ def should_fail(self):
         self.assertTrue(self.results.failures)
         self.assertFalse(self.results.errors)
 
+    def should_error(self):
+        self.assertTrue(self.results.errors)
+
     def test_contracts(self):
         spider = TestSpider()
 
@@ -209,6 +254,13 @@ def test_scrapes(self):
         request.callback(response)
         self.should_fail()
 
+    def test_custom_contracts(self):
+        self.conman.from_spider(CustomContractSuccessSpider(), self.results)
+        self.should_succeed()
+
+        self.conman.from_spider(CustomContractFailSpider(), self.results)
+        self.should_error()
+
     def test_errback(self):
         spider = TestSpider()
         response = ResponseMock()