Repository: dbt-labs/dbt-core

Issue:
`dbt run` summary reports incorrect SKIP= numbers
### Describe the bug
A clear and concise description of what the bug is. What command did you run? What happened?
When I do `dbt run --models some_model+`, if some_model fails, and it's dependencies SKIP, the final summary reports `Done. PASS=4 WARN=0 ERROR=1 SKIP=0 TOTAL=5`
### Steps To Reproduce
In as much detail as possible, please provide steps to reproduce the issue. Sample data that triggers the issue, example model code, etc is all very helpful here.

`dbt run --models any_model+` with dependencies, ensuring that `any_model` fails. The dependent models will SKIP, but the output will not record the number of skipped models.

### Expected behavior
A clear and concise description of what you expected to happen.

`dbt run --models any_model+` should report the correct number of skipped models in the final summary.

### Screenshots and log output
If applicable, add screenshots or log output to help explain your problem.

```Found 1306 models, 100 tests, 0 snapshots, 0 analyses, 324 macros, 1 operation, 9 seed files, 110 sources

19:02:14 | Concurrency: 4 threads (target='dev')
19:02:14 | 
19:02:14 | 1 of 5 START incremental model product_dev_user_name.parent_model [RUN]
19:02:18 | 1 of 5 ERROR creating incremental model product_dev_user_name.parent_model [ERROR in 3.69s]
19:02:19 | 2 of 5 SKIP relation product_dev_user_name.dependent_model1 [SKIP]
19:02:20 | 3 of 5 SKIP relation product_dev_user_name.dependent_model2 [SKIP]
19:02:20 | 4 of 5 SKIP relation product_dev_user_name.dependent_model3 [SKIP]
19:02:20 | 5 of 5 SKIP relation product_dev_user_name.dependent_model4 [SKIP]
19:02:22 | 
19:02:22 | Running 1 on-run-end hook
19:02:22 | 1 of 1 START hook: invision.on-run-end.0............................. [RUN]
19:02:33 | 1 of 1 OK hook: invision.on-run-end.0................................ [GRANT in 10.43s]
19:02:33 | 
19:02:33 | 
19:02:33 | Finished running 2 incremental models, 3 table models, 1 hook in 64.98s.

Completed with 1 error and 0 warnings:

Database Error in model v7_app_events__with_idle_time (models/product/parent_model.sql)
  relation "product_dev_user_name.missing_ref_example" does not exist
  compiled SQL at target/run/invision/product/parent_model.sql

Done. PASS=4 WARN=0 ERROR=1 SKIP=0 TOTAL=5
```

### System information
**Which database are you using dbt with?**
- [ ] postgres
- [x] redshift
- [ ] bigquery
- [ ] snowflake
- [ ] other (specify: ____________)


**The output of `dbt --version`:**
```
installed version: 0.15.1
   latest version: 0.15.2

Your version of dbt is out of date! You can find instructions for upgrading here:
https://docs.getdbt.com/docs/installation
```

**The operating system you're using:**
Whatever I get out of `docker build` via `FROM python:3.7.4`. If it becomes important I can figure it out.
**The output of `python --version`:**
`Python 3.7.4`
### Additional context
Add any other context about the problem here.



Discussion:
Here we go. The `RunModelResult` contract dropped the skipped definition, so it always returns false from the def in `PartialResult`. I think.

https://github.com/fishtown-analytics/dbt/commit/49f7cf8ecab4bd74b9b67958df4ace56353b1bed#diff-62f129966f7974678ed791d2e6eddc2bL184-L186
Thanks for the report @brangisom - I'm prioritizing this one for our 0.16.0 Barbara Gittings release. Is this something you'd be interested in contributing a fix for? 

I took a quick look here, and it the issue appears to be that we set the value `skip` on the `RunModelResult` object, but we report based on the `skipped` property, which is indeed always false!

I think the fix is to add a `skipped` property in the `WritableModelResult` class that returns the value of `self.skip`:

https://github.com/fishtown-analytics/dbt/blob/1d329e03645b8446899f71e7bd6bb92c0991a7dd/core/dbt/contracts/results.py#L68-L70

Set via

https://github.com/fishtown-analytics/dbt/blob/1d329e03645b8446899f71e7bd6bb92c0991a7dd/core/dbt/node_runners.py#L307-L317
> Thanks for the report @brangisom - I'm prioritizing this one for our 0.16.0 Barbara Gittings release. Is this something you'd be interested in contributing a fix for?

Yeah, I think I can knock that out this week.

Resolving patch:
diff --git a/core/dbt/contracts/results.py b/core/dbt/contracts/results.py
--- a/core/dbt/contracts/results.py
+++ b/core/dbt/contracts/results.py
@@ -69,6 +69,10 @@ def skipped(self):
 class WritableRunModelResult(PartialResult):
     skip: bool = False
 
+    @property
+    def skipped(self):
+        return self.skip
+
 
 @dataclass
 class RunModelResult(WritableRunModelResult):


Test patch:
diff --git a/test/integration/001_simple_copy_test/test_simple_copy.py b/test/integration/001_simple_copy_test/test_simple_copy.py
--- a/test/integration/001_simple_copy_test/test_simple_copy.py
+++ b/test/integration/001_simple_copy_test/test_simple_copy.py
@@ -1,10 +1,8 @@
-import io
 import json
 import os
 from pytest import mark
 
 from test.integration.base import DBTIntegrationTest, use_profile
-from dbt.logger import log_manager
 
 
 class BaseTestSimpleCopy(DBTIntegrationTest):
@@ -409,24 +407,14 @@ def project_config(self):
             "data-paths": [self.dir("seed-initial")],
         })
 
-    def setUp(self):
-        super().setUp()
-        self.initial_stdout = log_manager.stdout
-        self.initial_stderr = log_manager.stderr
-        self.stringbuf = io.StringIO()
-        log_manager.set_output_stream(self.stringbuf)
-
-    def tearDown(self):
-        log_manager.set_output_stream(self.initial_stdout, self.initial_stderr)
-        super().tearDown()
-
     def seed_get_json(self, expect_pass=True):
-        self.run_dbt(
+        results, output = self.run_dbt_and_capture(
             ['--debug', '--log-format=json', '--single-threaded', 'seed'],
             expect_pass=expect_pass
         )
+
         logs = []
-        for line in self.stringbuf.getvalue().split('\n'):
+        for line in output.split('\n'):
             try:
                 log = json.loads(line)
             except ValueError:
@@ -435,6 +423,7 @@ def seed_get_json(self, expect_pass=True):
             if log['extra'].get('run_state') != 'internal':
                 continue
             logs.append(log)
+
         self.assertGreater(len(logs), 0)
         return logs
 
diff --git a/test/integration/021_concurrency_test/test_concurrency.py b/test/integration/021_concurrency_test/test_concurrency.py
--- a/test/integration/021_concurrency_test/test_concurrency.py
+++ b/test/integration/021_concurrency_test/test_concurrency.py
@@ -26,7 +26,7 @@ def test__postgres__concurrency(self):
 
         self.run_sql_file("update.sql")
 
-        results = self.run_dbt(expect_pass=False)
+        results, output = self.run_dbt_and_capture(expect_pass=False)
         self.assertEqual(len(results), 7)
 
         self.assertTablesEqual("seed", "view_model")
@@ -36,6 +36,8 @@ def test__postgres__concurrency(self):
         self.assertTableDoesNotExist("invalid")
         self.assertTableDoesNotExist("skip")
 
+        self.assertIn('PASS=5 WARN=0 ERROR=1 SKIP=1 TOTAL=7', output)
+
     @use_profile('snowflake')
     def test__snowflake__concurrency(self):
         self.run_sql_file("seed.sql")
diff --git a/test/integration/base.py b/test/integration/base.py
--- a/test/integration/base.py
+++ b/test/integration/base.py
@@ -1,5 +1,6 @@
 import json
 import os
+import io
 import random
 import shutil
 import tempfile
@@ -537,6 +538,22 @@ def run_dbt(self, args=None, expect_pass=True, strict=True, parser=True, profile
 
         return res
 
+
+    def run_dbt_and_capture(self, *args, **kwargs):
+        try:
+            initial_stdout = log_manager.stdout
+            initial_stderr = log_manager.stderr
+            stringbuf = io.StringIO()
+            log_manager.set_output_stream(stringbuf)
+
+            res = self.run_dbt(*args, **kwargs)
+            stdout = stringbuf.getvalue()
+
+        finally:
+            log_manager.set_output_stream(initial_stdout, initial_stderr)
+
+        return res, stdout
+
     def run_dbt_and_check(self, args=None, strict=True, parser=False, profiles_dir=True):
         log_manager.reset_handlers()
         if args is None: