Repository: scoutapp/scout_apm_python

Issue:
Warn about wrong values for SCOUT_AGENT_TRIPLE
Follow-on to #239 - it's easy to override `SCOUT_AGENT_TRIPLE` to a wrong value. We should add some validation here with a log message if it looks wrong.


Discussion:

Resolving patch:
diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py
--- a/src/scout_apm/core/config.py
+++ b/src/scout_apm/core/config.py
@@ -192,10 +192,13 @@ def derive_socket_path(self):
         )
 
     def derive_core_agent_full_name(self):
+        triple = self.config.value("core_agent_triple")
+        if not platform_detection.is_valid_triple(triple):
+            logger.warning("Invalid value for core_agent_triple: %s", triple)
         return "{name}-{version}-{triple}".format(
             name="scout_apm_core",
             version=self.config.value("core_agent_version"),
-            triple=self.config.value("core_agent_triple"),
+            triple=triple,
         )
 
     def derive_core_agent_triple(self):
diff --git a/src/scout_apm/core/platform_detection.py b/src/scout_apm/core/platform_detection.py
--- a/src/scout_apm/core/platform_detection.py
+++ b/src/scout_apm/core/platform_detection.py
@@ -5,6 +5,16 @@
 import subprocess
 
 
+def is_valid_triple(triple):
+    values = triple.split("-", 1)
+    return (
+        len(values) == 2
+        and values[0] in ("i686", "x86_64", "unknown")
+        and values[1]
+        in ("unknown-linux-gnu", "unknown-linux-musl", "apple-darwin", "unknown")
+    )
+
+
 def get_triple():
     return "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,5 @@
 # coding=utf-8
+import logging
 import os
 
 import pytest
@@ -26,6 +27,14 @@
 TestApp.__test__ = False
 
 
+# Override built-in caplog fixture to always be at DEBUG level since we have
+# many DEBUG log messages
+@pytest.fixture()
+def caplog(caplog):
+    caplog.set_level(logging.DEBUG)
+    yield caplog
+
+
 class GlobalStateLeak(Exception):
     """Exception raised when a test leaks global state."""
 
diff --git a/tests/integration/core/test_core_agent_manager.py b/tests/integration/core/test_core_agent_manager.py
--- a/tests/integration/core/test_core_agent_manager.py
+++ b/tests/integration/core/test_core_agent_manager.py
@@ -52,7 +52,6 @@ def shutdown(core_agent_manager):
 
 
 def test_no_launch(caplog, core_agent_manager):
-    caplog.set_level(logging.DEBUG)
     ScoutConfig.set(core_agent_launch=False)
 
     try:
@@ -75,7 +74,6 @@ def test_no_launch(caplog, core_agent_manager):
 
 
 def test_no_verify(caplog, core_agent_manager):
-    caplog.set_level(logging.DEBUG)
     ScoutConfig.set(core_agent_download=False)
 
     try:
@@ -103,7 +101,6 @@ def test_download_and_launch(core_agent_manager):
 
 
 def test_verify_error(caplog, core_agent_manager):
-    caplog.set_level(logging.DEBUG)
     digest_patcher = mock.patch(
         "scout_apm.core.core_agent_manager.sha256_digest",
         return_value="not the expected digest",
@@ -127,6 +124,7 @@ def test_verify_error(caplog, core_agent_manager):
 
 
 def test_launch_error(caplog, core_agent_manager):
+    caplog.set_level(logging.ERROR)
     exception = ValueError("Hello Fail")
     with mock.patch(
         "scout_apm.core.core_agent_manager.CoreAgentManager.agent_binary",
diff --git a/tests/unit/core/samplers/test_cpu.py b/tests/unit/core/samplers/test_cpu.py
--- a/tests/unit/core/samplers/test_cpu.py
+++ b/tests/unit/core/samplers/test_cpu.py
@@ -23,7 +23,6 @@ def test_human_name():
 
 
 def test_run_negative_time_elapsed(caplog):
-    caplog.set_level(logging.DEBUG)
     cpu = Cpu()
     cpu.last_run = dt.datetime.utcnow() + dt.timedelta(days=100)
 
@@ -40,7 +39,6 @@ def test_run_negative_time_elapsed(caplog):
 
 
 def test_run_negative_last_cpu_times(caplog):
-    caplog.set_level(logging.DEBUG)
     cpu = Cpu()
     cpu.last_cpu_times = pcputimes(
         user=1e12, system=1e12, children_user=0.0, children_system=0.0
@@ -60,7 +58,6 @@ def test_run_negative_last_cpu_times(caplog):
 
 
 def test_run_within_zero_seconds(caplog):
-    caplog.set_level(logging.DEBUG)
     cpu = Cpu()
     # Force the calculation of normalized_wall_clock_elapsed to 0
     cpu.num_processors = 0
@@ -77,7 +74,6 @@ def test_run_within_zero_seconds(caplog):
 
 
 def test_run(caplog):
-    caplog.set_level(logging.DEBUG)
     cpu = Cpu()
 
     result = cpu.run()
@@ -93,7 +89,6 @@ def test_run(caplog):
 
 
 def test_run_undetermined_cpus(caplog):
-    caplog.set_level(logging.DEBUG)
     with mock.patch("psutil.cpu_count", return_value=None):
         cpu = Cpu()
 
diff --git a/tests/unit/core/samplers/test_memory.py b/tests/unit/core/samplers/test_memory.py
--- a/tests/unit/core/samplers/test_memory.py
+++ b/tests/unit/core/samplers/test_memory.py
@@ -54,8 +54,6 @@ def test_human_name():
 
 
 def test_run(caplog):
-    caplog.set_level(logging.DEBUG)
-
     result = Memory().run()
     assert isinstance(result, float) and result > 0.0
     record_tuples = [
diff --git a/tests/unit/core/test_config.py b/tests/unit/core/test_config.py
--- a/tests/unit/core/test_config.py
+++ b/tests/unit/core/test_config.py
@@ -1,6 +1,7 @@
 # coding=utf-8
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+import logging
 import os
 import re
 import sys
@@ -51,17 +52,37 @@ def test_get_derived_config_value():
         ScoutConfig.reset_all()
 
 
-def test_override_triple():
-    ScoutConfig.set(core_agent_triple="unknown-linux-musl")
+def test_override_triple(caplog):
+    triple = "unknown-unknown-linux-musl"
+    ScoutConfig.set(core_agent_triple=triple)
     config = ScoutConfig()
     try:
-        assert re.match(
-            r"scout_apm_core-v.*-unknown-linux-musl",
-            config.value("core_agent_full_name"),
-        )
+        full_name = config.value("core_agent_full_name")
+    finally:
+        ScoutConfig.reset_all()
+
+    assert full_name.endswith(triple)
+    assert caplog.record_tuples == []
+
+
+def test_override_triple_invalid(caplog):
+    bad_triple = "badtriple"
+    ScoutConfig.set(core_agent_triple=bad_triple)
+    config = ScoutConfig()
+    try:
+        full_name = config.value("core_agent_full_name")
     finally:
         ScoutConfig.reset_all()
 
+    assert full_name.endswith(bad_triple)
+    assert caplog.record_tuples == [
+        (
+            "scout_apm.core.config",
+            logging.WARNING,
+            "Invalid value for core_agent_triple: badtriple",
+        )
+    ]
+
 
 def test_get_default_config_value():
     config = ScoutConfig()
diff --git a/tests/unit/core/test_platform_detection.py b/tests/unit/core/test_platform_detection.py
--- a/tests/unit/core/test_platform_detection.py
+++ b/tests/unit/core/test_platform_detection.py
@@ -9,7 +9,25 @@
 
 
 def test_get_triple():
-    assert isinstance(platform_detection.get_triple(), string_type)
+    triple = platform_detection.get_triple()
+
+    assert isinstance(triple, string_type)
+    assert platform_detection.is_valid_triple(triple)
+
+
+@pytest.mark.parametrize(
+    "triple, validity",
+    [
+        ("x86_64-apple-darwin", True),
+        ("i686-unknown-linux-gnu", True),
+        ("unknown-unknown-linux-musl", True),
+        ("", False),
+        ("unknown", False),
+        ("---", False),
+    ],
+)
+def test_is_valid_triple(triple, validity):
+    assert platform_detection.is_valid_triple(triple) == validity
 
 
 @pytest.mark.parametrize(