Repository: scrapy/scrapy

Issue:
SCRAPY_CHECK is not set while running contract
### Description

Hi, it seems that #3739 is not doing what the [documentation describe](https://docs.scrapy.org/en/latest/topics/contracts.html#detecting-check-runs):

`os.environ.get('SCRAPY_CHECK')` is returning `None` in my contract check.

### Steps to Reproduce

1. Create a project from scratch
2. Add a random spider
3. Contract code is as follow
```
    def parse(self, response):
        """
        @url http://www.amazon.com/s?field-keywords=selfish+gene
        @returns requests 1 1
        """
        print("test", os.environ.get('SCRAPY_CHECK'))
        if os.environ.get('SCRAPY_CHECK'):
            yield scrapy.Request(url="next_url")
```

**Expected behavior:** Request should be yielded as per the documentation

**Actual behavior:** Nothing happen

**Reproduces how often:** In my local project and with fresh project

### Versions

Windows
```
(globenv) C:\Users\johnl>scrapy version --verbose
Scrapy       : 1.8.0
lxml         : 4.4.1.0
libxml2      : 2.9.5
cssselect    : 1.1.0
parsel       : 1.5.2
w3lib        : 1.21.0
Twisted      : 19.10.0
Python       : 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)]
pyOpenSSL    : 19.0.0 (OpenSSL 1.1.1c  28 May 2019)
cryptography : 2.7
Platform     : Windows-10-10.0.18362-SP0
```

Linux
```
scrapy version --verbose
Scrapy       : 1.8.0
lxml         : 4.4.1.0
libxml2      : 2.9.9
cssselect    : 1.1.0
parsel       : 1.5.2
w3lib        : 1.21.0
Twisted      : 19.7.0
Python       : 3.6.8 (default, Oct  7 2019, 12:59:55) - [GCC 8.3.0]
pyOpenSSL    : 19.0.0 (OpenSSL 1.1.1d  10 Sep 2019)
cryptography : 2.8
Platform     : Linux-4.4.0-18362-Microsoft-x86_64-with-Ubuntu-18.04-bionic
```



Discussion:
The variable is only defined at initialization, not at run time. If you have a look at the code example from the documentation, you’ll notice that it is used in `__init__`, not in a callback. I think this is on purpose, although it could be more clear in the documentation.

I’m turning this ticket into a documentation enhancement request. If you would rather it be a request to have the environment variable also available at run time, from callback, please let me know.
Thanks for the quick answer.

I think it could be nice to have it available for the callback running in check mode.
At least it would make sense to me that the ENV variable is set all the time while check.

But from there I can definitely build the check awareness feature that I want from `__init__` very easily.

EDIT: After giving a little bit more thought I think I will stay with doc enhancement as in my case it's more a trick in my spider that is forcing another trick!
While I agree that the current behaviour does match the docs, I wonder if this isn't a reasonable use case. From a very quick look, it seems like it could be solved by just including the next code block in the `set_environ` context manager:

```diff
diff --git scrapy/commands/check.py scrapy/commands/check.py
index 9d4437a4..09a76ca7 100644
--- scrapy/commands/check.py
+++ scrapy/commands/check.py
@@ -78,19 +78,19 @@ class Command(ScrapyCommand):
                 elif tested_methods:
                     self.crawler_process.crawl(spidercls)
 
-        # start checks
-        if opts.list:
-            for spider, methods in sorted(contract_reqs.items()):
-                if not methods and not opts.verbose:
-                    continue
-                print(spider)
-                for method in sorted(methods):
-                    print('  * %s' % method)
-        else:
-            start = time.time()
-            self.crawler_process.start()
-            stop = time.time()
-
-            result.printErrors()
-            result.printSummary(start, stop)
-            self.exitcode = int(not result.wasSuccessful())
+            # start checks
+            if opts.list:
+                for spider, methods in sorted(contract_reqs.items()):
+                    if not methods and not opts.verbose:
+                        continue
+                    print(spider)
+                    for method in sorted(methods):
+                        print('  * %s' % method)
+            else:
+                start = time.time()
+                self.crawler_process.start()
+                stop = time.time()
+
+                result.printErrors()
+                result.printSummary(start, stop)
+                self.exitcode = int(not result.wasSuccessful())
```

Any thoughts?
I have no strong opinion either way.

@Matthijsy Do you remember if you had a reason not to apply the environment variable at run time?
I am not sure why I did it that way. I think it would be fine to include the check run itself in the environment variable block as well.

Resolving patch:
diff --git a/scrapy/commands/check.py b/scrapy/commands/check.py
--- a/scrapy/commands/check.py
+++ b/scrapy/commands/check.py
@@ -78,19 +78,19 @@ def run(self, args, opts):
                 elif tested_methods:
                     self.crawler_process.crawl(spidercls)
 
-        # start checks
-        if opts.list:
-            for spider, methods in sorted(contract_reqs.items()):
-                if not methods and not opts.verbose:
-                    continue
-                print(spider)
-                for method in sorted(methods):
-                    print('  * %s' % method)
-        else:
-            start = time.time()
-            self.crawler_process.start()
-            stop = time.time()
-
-            result.printErrors()
-            result.printSummary(start, stop)
-            self.exitcode = int(not result.wasSuccessful())
+            # start checks
+            if opts.list:
+                for spider, methods in sorted(contract_reqs.items()):
+                    if not methods and not opts.verbose:
+                        continue
+                    print(spider)
+                    for method in sorted(methods):
+                        print('  * %s' % method)
+            else:
+                start = time.time()
+                self.crawler_process.start()
+                stop = time.time()
+
+                result.printErrors()
+                result.printSummary(start, stop)
+                self.exitcode = int(not result.wasSuccessful())


Test patch:
diff --git a/tests/test_command_check.py b/tests/test_command_check.py
new file mode 100644
--- /dev/null
+++ b/tests/test_command_check.py
@@ -0,0 +1,97 @@
+from os.path import join, abspath
+
+from tests.test_commands import CommandTest
+
+
+class CheckCommandTest(CommandTest):
+
+    command = 'check'
+
+    def setUp(self):
+        super(CheckCommandTest, self).setUp()
+        self.spider_name = 'check_spider'
+        self.spider = abspath(join(self.proj_mod_path, 'spiders', 'checkspider.py'))
+
+    def _write_contract(self, contracts, parse_def):
+        with open(self.spider, 'w') as file:
+            file.write("""
+import scrapy
+
+class CheckSpider(scrapy.Spider):
+    name = '{0}'
+    start_urls = ['http://example.com']
+
+    def parse(self, response, **cb_kwargs):
+        \"\"\"
+        @url http://example.com
+        {1}
+        \"\"\"
+        {2}
+            """.format(self.spider_name, contracts, parse_def))
+
+    def _test_contract(self, contracts='', parse_def='pass'):
+        self._write_contract(contracts, parse_def)
+        p, out, err = self.proc('check')
+        self.assertNotIn('F', out)
+        self.assertIn('OK', err)
+        self.assertEqual(p.returncode, 0)
+
+    def test_check_returns_requests_contract(self):
+        contracts = """
+        @returns requests 1
+        """
+        parse_def = """
+        yield scrapy.Request(url='http://next-url.com')
+        """
+        self._test_contract(contracts, parse_def)
+
+    def test_check_returns_items_contract(self):
+        contracts = """
+        @returns items 1
+        """
+        parse_def = """
+        yield {'key1': 'val1', 'key2': 'val2'}
+        """
+        self._test_contract(contracts, parse_def)
+
+    def test_check_cb_kwargs_contract(self):
+        contracts = """
+        @cb_kwargs {"arg1": "val1", "arg2": "val2"}
+        """
+        parse_def = """
+        if len(cb_kwargs.items()) == 0:
+            raise Exception("Callback args not set")
+        """
+        self._test_contract(contracts, parse_def)
+
+    def test_check_scrapes_contract(self):
+        contracts = """
+        @scrapes key1 key2
+        """
+        parse_def = """
+        yield {'key1': 'val1', 'key2': 'val2'}
+        """
+        self._test_contract(contracts, parse_def)
+
+    def test_check_all_default_contracts(self):
+        contracts = """
+        @returns items 1
+        @returns requests 1
+        @scrapes key1 key2
+        @cb_kwargs {"arg1": "val1", "arg2": "val2"}
+        """
+        parse_def = """
+        yield {'key1': 'val1', 'key2': 'val2'}
+        yield scrapy.Request(url='http://next-url.com')
+        if len(cb_kwargs.items()) == 0:
+            raise Exception("Callback args not set")
+        """
+        self._test_contract(contracts, parse_def)
+
+    def test_SCRAPY_CHECK_set(self):
+        parse_def = """
+        import os
+        if not os.environ.get('SCRAPY_CHECK'):
+            raise Exception('SCRAPY_CHECK not set')
+        """
+        self._test_contract(parse_def=parse_def)