pw_metric: Fix token paths in RPC service

This fixes the token paths in metrics returned from the RPC service, and
adds a corresponding test.

Change-Id: I74e0a7eba7d5e835f4f5b07f87ba7254f3b696a6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/25980
Commit-Queue: Keir Mierle <keir@google.com>
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
diff --git a/pw_metric/metric_service_nanopb.cc b/pw_metric/metric_service_nanopb.cc
index 49130a3..96d246d 100644
--- a/pw_metric/metric_service_nanopb.cc
+++ b/pw_metric/metric_service_nanopb.cc
@@ -91,7 +91,7 @@
 
   void Walk(const IntrusiveList<Metric>& metrics) {
     for (const auto& m : metrics) {
-      ScopedName(m.name(), *this);
+      ScopedName scoped_name(m.name(), *this);
       writer_.Write(m, path_);
     }
   }
@@ -103,7 +103,7 @@
   }
 
   void Walk(const Group& group) {
-    ScopedName(group.name(), *this);
+    ScopedName scoped_name(group.name(), *this);
     Walk(group.children());
     Walk(group.metrics());
   }
diff --git a/pw_metric/metric_service_nanopb_test.cc b/pw_metric/metric_service_nanopb_test.cc
index 26d6ebf..b2c3966 100644
--- a/pw_metric/metric_service_nanopb_test.cc
+++ b/pw_metric/metric_service_nanopb_test.cc
@@ -131,5 +131,77 @@
   // TODO(keir): Properly check all the fields.
 }
 
+bool TokenPathsMatch(uint32_t expected_token_path[5],
+                     const pw_metric_Metric& metric) {
+  // Calculate length of expected token & compare.
+  int expected_length = 0;
+  while (expected_token_path[expected_length]) {
+    expected_length++;
+  }
+  if (expected_length != metric.token_path_count) {
+    return false;
+  }
+
+  // Lengths match; so search the tokens themselves.
+  for (int i = 0; i < expected_length; ++i) {
+    if (expected_token_path[i] != metric.token_path[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+TEST(MetricService, TokenPaths) {
+  // Set up a nested group of metrics that will not fit in a single batch.
+  PW_METRIC_GROUP(root, "/");
+  PW_METRIC(root, a, "a", 1u);
+
+  PW_METRIC_GROUP(inner_1, "inner1");
+  PW_METRIC(inner_1, x, "x", 4u);
+  PW_METRIC(inner_1, z, "z", 6u);
+
+  PW_METRIC_GROUP(inner_2, "inner2");
+  PW_METRIC(inner_2, p, "p", 7u);
+  PW_METRIC(inner_2, u, "s", 12u);
+
+  root.Add(inner_1);
+  root.Add(inner_2);
+
+  // Run the RPC and ensure it completes.
+  MetricMethodContext context(root.metrics(), root.children());
+  context.call({});
+  EXPECT_TRUE(context.done());
+  EXPECT_EQ(Status::Ok(), context.status());
+
+  // The metrics should fit in one batch.
+  EXPECT_EQ(1u, context.responses().size());
+  EXPECT_EQ(5, context.responses()[0].metrics_count);
+
+  // Declare the token paths we expect to find.
+  // Note: This depends on the token variables from the PW_METRIC*() macros.
+  uint32_t expected_token_paths[5][5] = {
+      {a_token, 0u},
+      {inner_1_token, x_token, 0u},
+      {inner_1_token, z_token, 0u},
+      {inner_2_token, p_token, 0u},
+      {inner_2_token, u_token, 0u},
+  };
+
+  // For each expected token, search through all returned metrics to find it.
+  // The search is necessary since there is no guarantee of metric ordering.
+  for (auto& expected_token_path : expected_token_paths) {
+    int found_matches = 0;
+    // Note: There should only be 1 response.
+    for (const auto& response : context.responses()) {
+      for (unsigned m = 0; m < response.metrics_count; ++m) {
+        if (TokenPathsMatch(expected_token_path, response.metrics[m])) {
+          found_matches++;
+        }
+      }
+    }
+    EXPECT_EQ(found_matches, 1);
+  }
+}
+
 }  // namespace
 }  // namespace pw::metric