Repository: conan-io/conan

Issue:
None value and default value in options
- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

conan v1.6.1, Windows

I know that the value `None` has a special meening in options. I think the exact meening is not fully documented (see also https://github.com/conan-io/conan/issues/2780#issuecomment-383234689). 

In the following example the behaviour seems inconsistent:

```python
class libaconan(ConanFile):
    name = "liba"
    version = "1.0"
    options = {"opt": [None, "a"]}
    default_options = "opt=a"
    
    def configure(self):
        print("opt =", self.options.opt)
```

- `conan install liba/1.0@user/testing` prints `opt = a`
- `conan install liba/1.0@user/testing -o opt=a` prints `opt = a`
- `conan install liba/1.0@user/testing -o opt=None` prints `opt = None` (!)

But with libb consuming liba:

```python
class libbconan(ConanFile):
    name = "libb"
    version = "1.0"
    requires  = "liba/1.0@user/testing"
```

- `conan install libb/1.0@user/testing` prints `opt = a`.
- `conan install libb/1.0@user/testing -o liba:opt=a` prints `opt = a`.
- `conan install libb/1.0@user/testing -o liba:opt=None` prints `opt = a`. (!)





Discussion:
I think this issue would be a good addition to docs for Conan 1.8 once we have conan-io/conan#3424 merged
> conan install libb/1.0@user/testing -o liba:opt=None prints opt = a. (!)

We need to look into this as it would be a bug.
For me it is a bug related to the value ``None``. It works ok with the value ``False`` (when allowed for ``opt``, of course)

Resolving patch:
diff --git a/conans/model/options.py b/conans/model/options.py
--- a/conans/model/options.py
+++ b/conans/model/options.py
@@ -122,8 +122,7 @@ def propagate_upstream(self, down_package_values, down_ref, own_ref, package_nam
 
         assert isinstance(down_package_values, PackageOptionValues)
         for (name, value) in down_package_values.items():
-            current_value = self._dict.get(name)
-            if value == current_value:
+            if name in self._dict and self._dict.get(name) == value:
                 continue
 
             modified = self._modified.get(name)
@@ -463,8 +462,7 @@ def propagate_upstream(self, package_values, down_ref, own_ref, pattern_options)
             return
 
         for (name, value) in package_values.items():
-            current_value = self._data.get(name)
-            if value == current_value:
+            if name in self._data and self._data.get(name) == value:
                 continue
 
             modified = self._modified.get(name)


Test patch:
diff --git a/conans/test/model/options_test.py b/conans/test/model/options_test.py
--- a/conans/test/model/options_test.py
+++ b/conans/test/model/options_test.py
@@ -225,6 +225,34 @@ def pattern_unmatch_test(self):
                                                      ])
 
 
+class OptionsValuesPropagationUpstreamNone(unittest.TestCase):
+
+    def test_propagate_in_options(self):
+        package_options = PackageOptions.loads("""{opt: [None, "a", "b"],}""")
+        values = PackageOptionValues()
+        values.add_option("opt", "a")
+        package_options.values = values
+        sut = Options(package_options)
+
+        other_options = PackageOptionValues()
+        other_options.add_option("opt", None)
+        options = {"whatever.*": other_options}
+        down_ref = ConanFileReference.loads("Boost.Assert/0.1@diego/testing")
+        own_ref = ConanFileReference.loads("Boost.Assert/0.1@diego/testing")
+        sut.propagate_upstream(options, down_ref, own_ref)
+        self.assertEqual(sut.values.as_list(), [("opt", "a"),
+                                                ("whatever.*:opt", "None"),
+                                                ])
+
+    def test_propagate_in_pacakge_options(self):
+        package_options = PackageOptions.loads("""{opt: [None, "a", "b"],}""")
+        values = PackageOptionValues()
+        package_options.values = values
+
+        package_options.propagate_upstream({'opt': None}, None, None, [])
+        self.assertEqual(package_options.values.items(), [('opt', 'None'), ])
+
+
 class OptionsValuesTest(unittest.TestCase):
 
     def setUp(self):