Repository: ARMmbed/greentea

Issue:
Target property priority incorrect
Currently we have priority as follows:
```
internal yotta blob > targets.json > tool default
```
This is a bug.

Instead the priority should be:
```
targets.json /w default > internal yotta blob > tool delaut
```

This implies a few test cases:

In targets.json        | In yotta blob | property used | Currently Works
---------------------- | ------------- | ---------------- | ---------------
Yes, with property | No            | `targets.json`   | Yes
Yes, without property| No            | default   | Yes
Yes, with property | Yes           | `targets.json`   | No
Yes, without property | Yes           | default   | No
No                     | No           | default       | Yes
No                     | Yes           | yotta blob       | Yes

@bridadan Is this the issue masked by #248?






Discussion:

Patch:
diff --git a/mbed_greentea/mbed_target_info.py b/mbed_greentea/mbed_target_info.py
index 356676b..c825bcf 100644
--- a/mbed_greentea/mbed_target_info.py
+++ b/mbed_greentea/mbed_target_info.py
@@ -20,6 +20,17 @@ Author: Przemyslaw Wirkus <Przemyslaw.Wirkus@arm.com>
 import os
 import re
 import json
+from os import walk
+try:
+    from contextlib import suppress
+except ImportError:
+    from contextlib import contextmanager
+    @contextmanager
+    def suppress(*excs):
+        try:
+            yield
+        except excs:
+            pass
 from mbed_greentea.mbed_common_api import run_cli_process
 from mbed_greentea.mbed_greentea_log import gt_logger
 
@@ -381,82 +392,65 @@ def get_platform_property(platform, property):
     :return: property value, None if property not found
     """
 
-    # First load from targets.json if available
-    value_from_targets_json = get_platform_property_from_targets(platform, property)
-    if value_from_targets_json:
-        return value_from_targets_json
-
-    # Check if info is available for a specific platform
-    if platform in TARGET_INFO_MAPPING:
-        if property in TARGET_INFO_MAPPING[platform]['properties']:
-            return TARGET_INFO_MAPPING[platform]['properties'][property]
+    default = _get_platform_property_from_default(property)
+    from_targets_json = _get_platform_property_from_targets(
+        platform, property, default)
+    if from_targets_json:
+        return from_targets_json
+    from_info_mapping = _get_platform_property_from_info_mapping(platform, property)
+    if from_info_mapping:
+        return from_info_mapping
+    return default
+
+def _get_platform_property_from_default(property):
+    with suppress(KeyError):
+        return TARGET_INFO_MAPPING['default'][property]
+
+def _get_platform_property_from_info_mapping(platform, property):
+    with suppress(KeyError):
+        return TARGET_INFO_MAPPING[platform]['properties'][property]
+
+def _platform_property_from_targets_json(targets, platform, property, default):
+    """! Get a platforms's property from the target data structure in
+            targets.json. Takes into account target inheritance.
+    @param targets Data structure parsed from targets.json
+    @param platform Name of the platform
+    @param property Name of the property
+    @param default the fallback value if none is found, but the target exists
+    @return property value, None if property not found
 
-    # Check if default data is available
-    if 'default' in TARGET_INFO_MAPPING:
-        if property in TARGET_INFO_MAPPING['default']:
-            return TARGET_INFO_MAPPING['default'][property]
-
-    return None
+    """
+    with suppress(KeyError):
+        return targets[platform][property]
+    with suppress(KeyError):
+        for inherited_target in targets[platform]['inherits']:
+            result = _platform_property_from_targets_json(targets, inherited_target, property, None)
+            if result:
+                return result
+    if platform in targets:
+        return default
+
+IGNORED_DIRS = ['.build', 'BUILD', 'tools']
+
+def _find_targets_json(path):
+    for root, dirs, files in walk(path, followlinks=True):
+        for ignored_dir in IGNORED_DIRS:
+            if ignored_dir in dirs:
+                dirs.remove(ignored_dir)
+        if 'targets.json' in files:
+            yield os.path.join(root, 'targets.json')
 
-def get_platform_property_from_targets(platform, property):
+def _get_platform_property_from_targets(platform, property, default):
     """
     Load properties from targets.json file somewhere in the project structure
 
     :param platform:
     :return: property value, None if property not found
     """
-
-    def get_platform_property_from_targets(targets, platform, property):
-        """! Get a platforms's property from the target data structure in
-             targets.json. Takes into account target inheritance.
-        @param targets Data structure parsed from targets.json
-        @param platform Name of the platform
-        @param property Name of the property
-        @return property value, None if property not found
-
-        """
-
-        result = None
-        if platform in targets:
-            if property in targets[platform]:
-                result = targets[platform][property]
-            elif 'inherits' in targets[platform]:
-                result = None
-                for inherited_target in targets[platform]['inherits']:
-                    result = get_platform_property_from_targets(targets, inherited_target, property)
-
-                    # Stop searching after finding the first value for the property
-                    if result:
-                        break
-
-        return result
-
-    result = None
-    targets_json_path = []
-    for root, dirs, files in os.walk(os.getcwd(), followlinks=True):
-        ignored_dirs = ['.build', 'BUILD', 'tools']
-
-        for ignored_dir in ignored_dirs:
-            if ignored_dir in dirs:
-                dirs.remove(ignored_dir)
-
-        if 'targets.json' in files:
-            targets_json_path.append(os.path.join(root, 'targets.json'))
-
-    if not targets_json_path:
-        gt_logger.gt_log_warn("No targets.json files found, using default target properties")
-
-    for targets_path in targets_json_path:
-        try:
+    for targets_path in _find_targets_json(os.getcwd()):
+        with suppress(IOError, ValueError):
             with open(targets_path, 'r') as f:
                 targets = json.load(f)
-
-                # Load property from targets.json
-                result = get_platform_property_from_targets(targets, platform, property)
-
-                # If a valid property was found, stop looking
+                result = _platform_property_from_targets_json(targets, platform, property, default)
                 if result:
-                    break
-        except Exception:
-            continue
-    return result
+                    return result
diff --git a/setup.py b/setup.py
index e98e109..0734dfe 100644
--- a/setup.py
+++ b/setup.py
@@ -50,13 +50,15 @@ setup(name='mbed-greentea',
       license=LICENSE,
       test_suite = 'test',
       entry_points={
-        "console_scripts": ["mbedgt=mbed_greentea.mbed_greentea_cli:main",],
+          "console_scripts": ["mbedgt=mbed_greentea.mbed_greentea_cli:main",],
       },
       install_requires=["PrettyTable>=0.7.2",
-        "PySerial>=3.0",
-        "mbed-host-tests>=1.2.0",
-        "mbed-ls>=1.2.15",
-        "junit-xml",
-        "lockfile",
-        "mock",
-        "colorama>=0.3,<0.4"])
+                        "PySerial>=3.0",
+                        "mbed-host-tests>=1.2.0",
+                        "mbed-ls>=1.2.15",
+                        "junit-xml",
+                        "lockfile",
+                        "mock",
+                        "six",
+                        "colorama>=0.3,<0.4"])
+


Test patch:
diff --git a/test/mbed_gt_target_info.py b/test/mbed_gt_target_info.py
index e3f0a6a..96cd1db 100644
--- a/test/mbed_gt_target_info.py
+++ b/test/mbed_gt_target_info.py
@@ -21,6 +21,8 @@ import shutil
 import tempfile
 import unittest
 
+from six import StringIO
+
 from mock import patch
 from mbed_greentea import mbed_target_info
 
@@ -338,8 +340,168 @@ mbed-gcc 1.1.0
         result = mbed_target_info.add_target_info_mapping("null")
 
 
-    def test_get_platform_property_from_targets(self):
-        result = mbed_target_info.get_platform_property_from_targets({}, {})
+    def test_get_platform_property_from_targets_no_json(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find:
+            _find.return_value = iter([])
+            result = mbed_target_info._get_platform_property_from_targets("not_a_platform", "not_a_property", "default")
+            self.assertIsNone(result)
+
+    def test_get_platform_property_from_targets_no_file(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find,\
+             patch("mbed_greentea.mbed_target_info.open") as _open:
+            _find.return_value = iter(["foo"])
+            _open.side_effect = IOError
+            result = mbed_target_info._get_platform_property_from_targets("not_a_platform", "not_a_property", "default")
+            self.assertIsNone(result)
+
+    def test_get_platform_property_from_targets_invalid_json(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find,\
+             patch("mbed_greentea.mbed_target_info.open") as _open:
+            _find.return_value = iter(["foo"])
+            _open.return_value.__enter__.return_value = StringIO("{")
+            result = mbed_target_info._get_platform_property_from_targets("not_a_platform", "not_a_property", "default")
+            self.assertIsNone(result)
+
+    def test_get_platform_property_from_targets_empty_json(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find,\
+             patch("mbed_greentea.mbed_target_info.open") as _open:
+            _find.return_value = iter(["foo"])
+            _open.return_value.__enter__.return_value = StringIO("{}")
+            result = mbed_target_info._get_platform_property_from_targets("not_a_platform", "not_a_property", "default")
+            self.assertIsNone(result)
+
+    def test_get_platform_property_from_targets_no_value(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find,\
+             patch("mbed_greentea.mbed_target_info.open") as _open:
+            _find.return_value = iter(["foo"])
+            _open.return_value.__enter__.return_value = StringIO("{\"K64F\": {}}")
+            result = mbed_target_info._get_platform_property_from_targets("K64F", "not_a_property", "default")
+            self.assertEqual(result, "default")
+
+    def test_get_platform_property_from_targets_in_json(self):
+        with patch("mbed_greentea.mbed_target_info._find_targets_json") as _find,\
+             patch("mbed_greentea.mbed_target_info.open") as _open:
+            _find.return_value = iter(["foo"])
+            _open.return_value.__enter__.return_value = StringIO("{\"K64F\": {\"copy_method\": \"cp\"}}")
+            result = mbed_target_info._get_platform_property_from_targets("K64F", "copy_method", "default")
+            self.assertEqual("cp", result)
+
+    def test_find_targets_json(self):
+        with patch("mbed_greentea.mbed_target_info.walk") as _walk:
+            _walk.return_value = iter([("", ["foo"], []), ("foo", [], ["targets.json"])])
+            result = list(mbed_target_info._find_targets_json("bogus_path"))
+            self.assertEqual(result, ["foo/targets.json"])
+
+    def test_find_targets_json_ignored(self):
+        with patch("mbed_greentea.mbed_target_info.walk") as _walk:
+            walk_result =[("", [".build"], [])]
+            _walk.return_value = iter(walk_result)
+            result = list(mbed_target_info._find_targets_json("bogus_path"))
+            self.assertEqual(result, [])
+            self.assertEqual(walk_result, [("", [], [])])
+
+    def test_platform_property_from_targets_json_empty(self):
+        result = mbed_target_info._platform_property_from_targets_json(
+            {}, "not_a_target", "not_a_property", "default"
+        )
+        self.assertIsNone(result)
+
+    def test_platform_property_from_targets_json_base_target(self):
+        result = mbed_target_info._platform_property_from_targets_json(
+            {"K64F": {"copy_method": "cp"}}, "K64F", "copy_method", "default"
+        )
+        self.assertEqual(result, "cp")
+
+    def test_platform_property_from_targets_json_inherits(self):
+        result = mbed_target_info._platform_property_from_targets_json(
+            {"K64F": {"inherits": ["Target"]}, "Target": {"copy_method": "cp"}},
+            "K64F", "copy_method", "default"
+        )
+        self.assertEqual(result, "cp")
+
+    def test_platform_property_from_default_missing(self):
+        result = mbed_target_info._get_platform_property_from_default("not_a_property")
+        self.assertIsNone(result)
+
+    def test_platform_property_from_default(self):
+        result = mbed_target_info._get_platform_property_from_default("copy_method")
+        self.assertEqual(result, "default")
+
+    def test_platform_property_from_info_mapping_bad_platform(self):
+        result = mbed_target_info._get_platform_property_from_info_mapping("not_a_platform", "not_a_property")
+        self.assertIsNone(result)
+
+    def test_platform_property_from_info_mapping_missing(self):
+        result = mbed_target_info._get_platform_property_from_info_mapping("K64F", "not_a_property")
+        self.assertIsNone(result)
+
+    def test_platform_property_from_info_mapping(self):
+        result = mbed_target_info._get_platform_property_from_info_mapping("K64F", "copy_method")
+        self.assertEqual(result, "default")
+
+
+    # The following test cases are taken from this table:
+    #
+    # Num | In targets.json | In yotta blob | In Default | property used
+    # --- | --------------- | ------------- | ---------- | --------------
+    # 1   | Yes             | No            | Yes        |`targets.json`
+    # 2   | Yes             | Yes           | Yes        |`targets.json`
+    # 3   | No              | Yes           | Yes        | yotta blob
+    # 4   | No              | No            | Yes        | default
+    # 5   | No              | No            | No         | None
+    # 6   | Yes             | No            | No         |`targets.json`
+    # 7   | Yes             | Yes           | No         |`targets.json`
+    # 8   | No              | Yes           | No         | yotta blob
+    def test_platform_property(self):
+        """Test that platform_property picks the property value preserving
+        the following priority relationship:
+        targets.json > yotta blob > default
+        """
+        with patch("mbed_greentea.mbed_target_info._get_platform_property_from_targets") as _targets,\
+             patch("mbed_greentea.mbed_target_info._get_platform_property_from_info_mapping") as _info_mapping,\
+             patch("mbed_greentea.mbed_target_info._get_platform_property_from_default") as _default:
+            # 1
+            _targets.return_value = "targets"
+            _info_mapping.return_value = None
+            _default.return_value = "default"
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "targets")
+            # 2
+            _info_mapping.return_value = "yotta"
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "targets")
+            # 3
+            _targets.return_value = None
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "yotta")
+            # 4
+            _info_mapping.return_value = None
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "default")
+            # 5
+            _default.return_value = None
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                None)
+            # 6
+            _targets.return_value = "targets"
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "targets")
+            # 7
+            _info_mapping.return_value = "yotta"
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "targets")
+            # 8
+            _targets.return_value = None
+            self.assertEqual(
+                mbed_target_info.get_platform_property("K64F", "copy_method"),
+                "yotta")
 
 
     def test_parse_yotta_json_for_build_name(self):