From aa70f35c5e36bdfeff4073e92b41d0a209cf4a76 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Saugat=20Pachhai=20=28=E0=A4=B8=E0=A5=8C=E0=A4=97=E0=A4=BE?=
 =?UTF-8?q?=E0=A4=A4=29?= <suagatchhetri@outlook.com>
Date: Thu, 9 Jul 2020 17:36:39 +0545
Subject: [PATCH] params: fix skipping of params dvc.lock when it's a falsy
 value

The falsy values were being skipped when merging params values
from dvc.yaml and dvc.lock, showing the values as always changed.

These values were never added to `info`, but were found in
`self.params` making them appear as changed.

Fixes #4184
---
 dvc/dependency/param.py              |  5 ++---
 tests/func/test_run_multistage.py    |  2 +-
 tests/unit/dependency/test_params.py | 30 +++++++++++++++++++++++-----
 3 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/dvc/dependency/param.py b/dvc/dependency/param.py
index 4df6087f52..12860e3475 100644
--- a/dvc/dependency/param.py
+++ b/dvc/dependency/param.py
@@ -45,9 +45,8 @@ def fill_values(self, values=None):
         if not values:
             return
         for param in self.params:
-            value = values.get(param)
-            if value:
-                self.info[param] = value
+            if param in values:
+                self.info[param] = values[param]
 
     def save(self):
         super().save()
diff --git a/tests/func/test_run_multistage.py b/tests/func/test_run_multistage.py
index daf9c0c2ee..056052342a 100644
--- a/tests/func/test_run_multistage.py
+++ b/tests/func/test_run_multistage.py
@@ -244,7 +244,7 @@ def test_run_params_default(tmp_dir, dvc):
         params=["nested.nested1.nested2"],
         cmd="cat params.yaml",
     )
-    isinstance(stage.deps[0], ParamsDependency)
+    assert isinstance(stage.deps[0], ParamsDependency)
     assert stage.deps[0].params == ["nested.nested1.nested2"]
 
     lockfile = stage.dvcfile._lockfile
diff --git a/tests/unit/dependency/test_params.py b/tests/unit/dependency/test_params.py
index b4edf53d6e..2667feabb6 100644
--- a/tests/unit/dependency/test_params.py
+++ b/tests/unit/dependency/test_params.py
@@ -4,6 +4,7 @@
 from dvc.dependency import ParamsDependency, loadd_from, loads_params
 from dvc.dependency.param import BadParamFileError, MissingParamsError
 from dvc.stage import Stage
+from dvc.utils.yaml import load_yaml
 
 PARAMS = {
     "foo": 1,
@@ -11,6 +12,7 @@
     "baz": "str",
     "qux": None,
 }
+DEFAULT_PARAMS_FILE = ParamsDependency.DEFAULT_PARAMS_FILE
 
 
 def test_loads_params(dvc):
@@ -63,7 +65,7 @@ def test_loadd_from(dvc):
 def test_dumpd_with_info(dvc):
     dep = ParamsDependency(Stage(dvc), None, PARAMS)
     assert dep.dumpd() == {
-        "path": "params.yaml",
+        "path": DEFAULT_PARAMS_FILE,
         "params": PARAMS,
     }
 
@@ -71,7 +73,7 @@ def test_dumpd_with_info(dvc):
 def test_dumpd_without_info(dvc):
     dep = ParamsDependency(Stage(dvc), None, list(PARAMS.keys()))
     assert dep.dumpd() == {
-        "path": "params.yaml",
+        "path": DEFAULT_PARAMS_FILE,
         "params": list(PARAMS.keys()),
     }
 
@@ -82,7 +84,7 @@ def test_read_params_nonexistent_file(dvc):
 
 
 def test_read_params_unsupported_format(tmp_dir, dvc):
-    tmp_dir.gen("params.yaml", b"\0\1\2\3\4\5\6\7")
+    tmp_dir.gen(DEFAULT_PARAMS_FILE, b"\0\1\2\3\4\5\6\7")
     dep = ParamsDependency(Stage(dvc), None, ["foo"])
     with pytest.raises(BadParamFileError):
         dep.read_params()
@@ -90,7 +92,8 @@ def test_read_params_unsupported_format(tmp_dir, dvc):
 
 def test_read_params_nested(tmp_dir, dvc):
     tmp_dir.gen(
-        "params.yaml", yaml.dump({"some": {"path": {"foo": ["val1", "val2"]}}})
+        DEFAULT_PARAMS_FILE,
+        yaml.dump({"some": {"path": {"foo": ["val1", "val2"]}}}),
     )
     dep = ParamsDependency(Stage(dvc), None, ["some.path.foo"])
     assert dep.read_params() == {"some.path.foo": ["val1", "val2"]}
@@ -103,7 +106,24 @@ def test_save_info_missing_config(dvc):
 
 
 def test_save_info_missing_param(tmp_dir, dvc):
-    tmp_dir.gen("params.yaml", "bar: baz")
+    tmp_dir.gen(DEFAULT_PARAMS_FILE, "bar: baz")
     dep = ParamsDependency(Stage(dvc), None, ["foo"])
     with pytest.raises(MissingParamsError):
         dep.save_info()
+
+
+@pytest.mark.parametrize(
+    "param_value",
+    ["", "false", "[]", "{}", "null", "no", "off"]
+    # we use pyyaml to load params.yaml, which only supports YAML 1.1
+    # so, some of the above are boolean values
+)
+def test_params_with_false_values(tmp_dir, dvc, param_value):
+    key = "param"
+    dep = ParamsDependency(Stage(dvc), DEFAULT_PARAMS_FILE, [key])
+    (tmp_dir / DEFAULT_PARAMS_FILE).write_text(f"{key}: {param_value}")
+
+    dep.fill_values(load_yaml(DEFAULT_PARAMS_FILE))
+
+    with dvc.state:
+        assert dep.status() == {}