This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch fix/mal-list-of-and-test-split
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit 03322bf76736fe0fb2ed108ec5b12c82a6f0daf3
Author: Wu Sheng <[email protected]>
AuthorDate: Tue Mar 17 11:08:27 2026 +0800

    Fix MAL List.of() varargs for >10 elements and split test class by scope
---
 .../analyzer/v2/compiler/MALClassGenerator.java    |  10 +-
 .../v2/compiler/MALClassGeneratorClosureTest.java  | 238 ++++++++++++++++++++
 .../v2/compiler/MALClassGeneratorScopeTest.java    | 243 +++++++++++++++++++++
 .../v2/compiler/MALClassGeneratorTest.java         | 240 +-------------------
 4 files changed, 495 insertions(+), 236 deletions(-)

diff --git 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
index cfaff7d829..465cbddbac 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
@@ -919,18 +919,20 @@ public final class MALClassGenerator {
         } else if (arg instanceof MALExpressionModel.StringListArgument) {
             final List<String> vals =
                 ((MALExpressionModel.StringListArgument) arg).getValues();
-            sb.append("java.util.List.of(");
+            // Use Arrays.asList — Javassist cannot resolve List.of() varargs 
for >10 elements.
+            sb.append("java.util.Arrays.asList(new String[]{");
             for (int i = 0; i < vals.size(); i++) {
                 if (i > 0) {
                     sb.append(", ");
                 }
                 
sb.append('"').append(MALCodegenHelper.escapeJava(vals.get(i))).append('"');
             }
-            sb.append(')');
+            sb.append("})");
         } else if (arg instanceof MALExpressionModel.NumberListArgument) {
             final List<Double> vals =
                 ((MALExpressionModel.NumberListArgument) arg).getValues();
-            sb.append("java.util.List.of(");
+            // Use Arrays.asList — Javassist cannot resolve List.of() varargs 
for >10 elements.
+            sb.append("java.util.Arrays.asList(new Number[]{");
             for (int i = 0; i < vals.size(); i++) {
                 if (i > 0) {
                     sb.append(", ");
@@ -942,7 +944,7 @@ public final class MALClassGenerator {
                     sb.append("Double.valueOf(").append(v).append(')');
                 }
             }
-            sb.append(')');
+            sb.append("})");
         } else if (arg instanceof MALExpressionModel.BoolArgument) {
             sb.append(((MALExpressionModel.BoolArgument) arg).isValue());
         } else if (arg instanceof MALExpressionModel.NullArgument) {
diff --git 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorClosureTest.java
 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorClosureTest.java
new file mode 100644
index 0000000000..3dcbce0f6a
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorClosureTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.v2.compiler;
+
+import javassist.ClassPool;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.MalExpression;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Closure-related tests for MALClassGenerator: tag closures, forEach closures,
+ * ProcessRegistry FQCN resolution, network-profiling patterns, string 
concatenation,
+ * regex match, and time() scalar.
+ */
+class MALClassGeneratorClosureTest {
+
+    private MALClassGenerator generator;
+
+    @BeforeEach
+    void setUp() {
+        generator = new MALClassGenerator(new ClassPool(true));
+    }
+
+    // ==================== Closure key extraction tests ====================
+
+    @Test
+    void tagClosurePutsCorrectKey() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_key",
+            "metric.tag({tags -> tags.cluster = 'activemq::' + 
tags.cluster})");
+        assertNotNull(expr);
+        final String source = generator.generateSource(
+            "metric.tag({tags -> tags.cluster = 'activemq::' + 
tags.cluster})");
+        assertTrue(source.contains("this._tag"),
+            "Generated source should reference pre-compiled closure");
+    }
+
+    @Test
+    void tagClosureKeyExtractionViaGeneratedCode() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_key_gen",
+            "metric.tag({tags -> tags.service_name = 'svc1'})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void tagClosureBracketAssignment() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_bracket",
+            "metric.tag({tags -> tags['my_key'] = 'my_value'})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    // ==================== forEach closure tests ====================
+
+    @Test
+    void forEachClosureCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_foreach",
+            "metric.forEach(['client', 'server'], {prefix, tags ->"
+            + " tags[prefix + '_name'] = 'value'})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void forEachClosureWithBareReturn() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_foreach_return",
+            "metric.forEach(['x'], {prefix, tags ->\n"
+            + "  if (tags[prefix + '_id'] != null) {\n"
+            + "    return\n"
+            + "  }\n"
+            + "  tags[prefix + '_id'] = 'default'\n"
+            + "})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void forEachClosureWithVarDeclAndElseIf() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_foreach_vars",
+            "metric.forEach(['component'], {key, tags ->\n"
+            + "  String result = \"\"\n"
+            + "  String protocol = tags['protocol']\n"
+            + "  String ssl = tags['is_ssl']\n"
+            + "  if (protocol == 'http' && ssl == 'true') {\n"
+            + "    result = '129'\n"
+            + "  } else if (protocol == 'http') {\n"
+            + "    result = '49'\n"
+            + "  } else if (ssl == 'true') {\n"
+            + "    result = '130'\n"
+            + "  } else {\n"
+            + "    result = '110'\n"
+            + "  }\n"
+            + "  tags[key] = result\n"
+            + "})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    // ==================== ProcessRegistry FQCN resolution tests 
====================
+
+    @Test
+    void processRegistryResolvedToFQCN() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_registry",
+            "metric.forEach(['client'], {prefix, tags ->\n"
+            + "  tags[prefix + '_process_id'] = "
+            + "ProcessRegistry.generateVirtualLocalProcess(tags.service, 
tags.instance)\n"
+            + "})");
+        assertNotNull(expr);
+    }
+
+    // ==================== Network-profiling full expression tests 
====================
+
+    @Test
+    void networkProfilingFirstClosureCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_np1",
+            "metric.forEach(['client', 'server'], { prefix, tags ->\n"
+            + "    if (tags[prefix + '_process_id'] != null) {\n"
+            + "      return\n"
+            + "    }\n"
+            + "    if (tags[prefix + '_local'] == 'true') {\n"
+            + "      tags[prefix + '_process_id'] = ProcessRegistry"
+            + ".generateVirtualLocalProcess(tags.service, tags.instance)\n"
+            + "      return\n"
+            + "    }\n"
+            + "    tags[prefix + '_process_id'] = ProcessRegistry"
+            + ".generateVirtualRemoteProcess(tags.service, tags.instance,"
+            + " tags[prefix + '_address'])\n"
+            + "  })");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void networkProfilingSecondClosureCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_np2",
+            "metric.forEach(['component'], { key, tags ->\n"
+            + "    String result = \"\"\n"
+            + "    // protocol are defined in the component-libraries.yml\n"
+            + "    String protocol = tags['protocol']\n"
+            + "    String ssl = tags['is_ssl']\n"
+            + "    if (protocol == 'http' && ssl == 'true') {\n"
+            + "      result = '129'\n"
+            + "    } else if (protocol == 'http') {\n"
+            + "      result = '49'\n"
+            + "    } else if (ssl == 'true') {\n"
+            + "      result = '130'\n"
+            + "    } else {\n"
+            + "      result = '110'\n"
+            + "    }\n"
+            + "    tags[key] = result\n"
+            + "  })");
+        assertNotNull(expr);
+    }
+
+    // ==================== String concatenation and regex in closures 
====================
+
+    @Test
+    void apisixExpressionCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_apisix",
+            "metric.tag({tags -> tags.service_name = 'APISIX::'"
+            + "+(tags['skywalking_service']?.trim()?:'APISIX')})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void closureStringConcatenation() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_concat",
+            "metric.tag({tags -> tags.service_name = 'APISIX::' + 
tags.service})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void regexMatchWithDefCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_regex",
+            "metric.tag({tags ->\n"
+            + "  def matcher = (tags.metrics_name =~ 
/\\.ssl\\.certificate\\.([^.]+)\\.expiration/)\n"
+            + "  tags.secret_name = matcher ? matcher[0][1] : \"unknown\"\n"
+            + "})");
+        assertNotNull(expr);
+        assertNotNull(expr.run(java.util.Map.of()));
+    }
+
+    @Test
+    void envoyCAExpressionCompiles() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_envoy_ca",
+            "(metric.tagMatch('metrics_name', 
'.*ssl.*expiration_unix_time_seconds')"
+            + ".tag({tags ->\n"
+            + "  def matcher = (tags.metrics_name =~ 
/\\.ssl\\.certificate\\.([^.]+)"
+            + "\\.expiration_unix_time_seconds/)\n"
+            + "  tags.secret_name = matcher ? matcher[0][1] : \"unknown\"\n"
+            + "}).min(['app', 'secret_name']) - time())"
+            + ".downsampling(MIN).service(['app'], Layer.MESH_DP)");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void timeScalarFunctionHandledInMetadata() throws Exception {
+        final MalExpression expr = generator.compile(
+            "test_time",
+            "(metric.sum(['app']) - time()).service(['app'], Layer.GENERAL)");
+        assertNotNull(expr);
+        assertNotNull(expr.metadata());
+        assertTrue(expr.metadata().getSamples().contains("metric"));
+        assertTrue(expr.metadata().getSamples().size() == 1);
+    }
+}
diff --git 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorScopeTest.java
 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorScopeTest.java
new file mode 100644
index 0000000000..2c994920d7
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorScopeTest.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.v2.compiler;
+
+import javassist.ClassPool;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Full-expression compilation tests combining expPrefix + exp + expSuffix via 
formatExp.
+ * Covers endpoint, service, instance, serviceRelation scope suffixes,
+ * forEach closures with if/else-if/else chains, sum() with >10 label keys,
+ * and tag() closure chained with service() suffix.
+ */
+class MALClassGeneratorScopeTest {
+
+    private MALClassGenerator generator;
+
+    @BeforeEach
+    void setUp() {
+        generator = new MALClassGenerator(new ClassPool(true));
+    }
+
+    /**
+     * Simulates MetricConvert.formatExp() to build the full expression
+     * from expPrefix + exp + expSuffix.
+     */
+    private static String formatExp(String expPrefix, String expSuffix, String 
exp) {
+        String ret = exp;
+        if (expPrefix != null && !expPrefix.isEmpty()) {
+            int dotIdx = exp.indexOf('.');
+            if (dotIdx > 0) {
+                ret = String.format("(%s.%s)", exp.substring(0, dotIdx), 
expPrefix);
+                String after = exp.substring(dotIdx + 1);
+                if (!after.isEmpty()) {
+                    ret = String.format("(%s.%s)", ret, after);
+                }
+            }
+        }
+        if (expSuffix != null && !expSuffix.isEmpty()) {
+            ret = String.format("(%s).%s", ret, expSuffix);
+        }
+        return ret;
+    }
+
+    private void compileRule(String name, String expPrefix, String expSuffix, 
String exp)
+            throws Exception {
+        final String full = formatExp(expPrefix, expSuffix, exp);
+        assertNotNull(generator.compile(name, full));
+    }
+
+    // --- Shared constants ---
+
+    private static final String FOREACH_COMPONENT_PREFIX =
+        "forEach(['component'], { key, tags ->\n"
+        + "    String result = \"\"\n"
+        + "    String protocol = tags['protocol']\n"
+        + "    String ssl = tags['is_ssl']\n"
+        + "    if (protocol == 'http' && ssl == 'true') {\n"
+        + "      result = '129'\n"
+        + "    } else if (protocol == 'http') {\n"
+        + "      result = '49'\n"
+        + "    } else if (ssl == 'true') {\n"
+        + "      result = '130'\n"
+        + "    } else {\n"
+        + "      result = '110'\n"
+        + "    }\n"
+        + "    tags[key] = result\n"
+        + "  })";
+
+    private static final String RELATION_SERVER_SUFFIX =
+        "serviceRelation(DetectPoint.SERVER, "
+        + "[\"client_service_subset\", \"client_service_name\", 
\"client_namespace\", \"client_service_cluster\", \"client_service_env\"], "
+        + "[\"server_service_subset\", \"server_service_name\", 
\"server_namespace\", \"server_service_cluster\", \"server_service_env\"], "
+        + "'|', Layer.GENERAL, 'component')";
+
+    private static final String RELATION_CLIENT_SUFFIX =
+        "serviceRelation(DetectPoint.CLIENT, "
+        + "[\"client_service_subset\", \"client_service_name\", 
\"client_namespace\", \"client_service_cluster\", \"client_service_env\"], "
+        + "[\"server_service_subset\", \"server_service_name\", 
\"server_namespace\", \"server_service_cluster\", \"server_service_env\"], "
+        + "'|', Layer.GENERAL, 'component')";
+
+    private static final String HISTOGRAM_12_LABELS =
+        ".sum([\"le\", \"client_service_subset\", \"client_service_name\", 
\"client_namespace\", "
+        + "\"client_service_cluster\", \"client_service_env\", 
\"server_service_subset\", "
+        + "\"server_service_name\", \"server_namespace\", 
\"server_service_cluster\", "
+        + "\"server_service_env\", 'component'])"
+        + ".downsampling(SUM).histogram().histogram_percentile([50, 75, 90, 
95, 99])";
+
+    // ==================== endpoint scope ====================
+
+    @Test
+    void endpointScopeWithHistogramPercentile() throws Exception {
+        final String suffix = "endpoint([\"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\"], [\"api\"], \"|\", 
Layer.GENERAL)";
+        compileRule("ep_cpm", null, suffix,
+            "api_agent_api_total_count.downsampling(SUM_PER_MIN)");
+        compileRule("ep_duration", null, suffix,
+            "api_agent_api_total_duration.downsampling(SUM_PER_MIN)");
+        compileRule("ep_success", null, suffix,
+            "api_agent_api_http_success_count.downsampling(SUM_PER_MIN)");
+        compileRule("ep_percentile", null, suffix,
+            "api_agent_api_response_time_histogram"
+            + ".sum([\"le\", \"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\", \"api\"])"
+            + ".downsampling(SUM).histogram().histogram_percentile([50, 75, 
90, 95, 99])");
+    }
+
+    // ==================== service scope ====================
+
+    @Test
+    void serviceScopeWithDelimiter() throws Exception {
+        final String suffix = "service([\"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\"], \"|\", 
Layer.GENERAL)";
+        compileRule("svc_api_cpm", null, suffix,
+            "api_agent_api_total_count.downsampling(SUM_PER_MIN)");
+        compileRule("svc_api_duration", null, suffix,
+            "api_agent_api_total_duration.downsampling(SUM_PER_MIN)");
+        compileRule("svc_api_success", null, suffix,
+            "api_agent_api_http_success_count.downsampling(SUM_PER_MIN)");
+        compileRule("svc_api_percentile", null, suffix,
+            "api_agent_api_response_time_histogram"
+            + ".sum([\"le\", \"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\"])"
+            + ".downsampling(SUM).histogram().histogram_percentile([50, 75, 
90, 95, 99])");
+    }
+
+    @Test
+    void serviceScopeWithTcpMetrics() throws Exception {
+        final String suffix = "service([\"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\"], \"|\", 
Layer.GENERAL)";
+        for (final String metric : new String[]{
+                "write_bytes", "write_count", "write_execute_time",
+                "read_bytes", "read_count", "read_execute_time",
+                "connect_connection_count", "connect_connection_time",
+                "accept_connection_count", "accept_connection_time",
+                "close_connection_count", "close_connection_time"}) {
+            compileRule("svc_tcp_" + metric, null, suffix,
+                "metric_tcp_" + metric + ".downsampling(SUM_PER_MIN)");
+        }
+    }
+
+    @Test
+    void tagClosureChainedWithServiceSuffix() throws Exception {
+        final String suffix = "tag({tags -> tags.service = 'satellite::' + 
tags.service}).service(['service'], Layer.SO11Y_SATELLITE)";
+        compileRule("sat_receive", null, suffix,
+            "sw_stl_gatherer_receive_count.sum([\"pipe\", \"status\", 
\"service\"]).increase(\"PT1M\")");
+        compileRule("sat_fetch", null, suffix,
+            "sw_stl_gatherer_fetch_count.sum([\"pipe\", \"status\", 
\"service\"]).increase(\"PT1M\")");
+        compileRule("sat_queue_in", null, suffix,
+            "sw_stl_queue_output_count.sum([\"pipe\", \"status\", 
\"service\"]).increase(\"PT1M\")");
+        compileRule("sat_send", null, suffix,
+            "sw_stl_sender_output_count.sum([\"pipe\", \"status\", 
\"service\"]).increase(\"PT1M\")");
+        compileRule("sat_queue_cap", null, suffix,
+            "sw_stl_pipeline_queue_total_capacity.sum([\"pipeline\", 
\"service\"])");
+        compileRule("sat_queue_used", null, suffix,
+            "sw_stl_pipeline_queue_partition_size.sum([\"pipeline\", 
\"service\"])");
+        compileRule("sat_cpu", null, suffix,
+            "sw_stl_grpc_server_cpu_gauge.downsampling(LATEST)");
+        compileRule("sat_conn", null, suffix,
+            "sw_stl_grpc_server_connection_count.downsampling(LATEST)");
+    }
+
+    // ==================== instance scope ====================
+
+    @Test
+    void instanceScopeWithNullLayerKey() throws Exception {
+        final String suffix = "instance([\"service_subset\", \"service_name\", 
\"k8s_namespace\", \"service_cluster\", \"service_env\"], \"|\", 
[\"instance_name\"], \"|\", Layer.GENERAL, null)";
+        for (final String metric : new String[]{
+                "write_bytes", "write_count", "write_execute_time",
+                "read_bytes", "read_count", "read_execute_time",
+                "connect_connection_count", "connect_connection_time",
+                "accept_connection_count", "accept_connection_time",
+                "close_connection_count", "close_connection_time"}) {
+            compileRule("inst_tcp_" + metric, null, suffix,
+                "api_agent_tcp_" + metric + ".downsampling(SUM_PER_MIN)");
+        }
+    }
+
+    // ==================== serviceRelation scope ====================
+
+    @Test
+    void serviceRelation6ArgWithForEachServer() throws Exception {
+        compileRule("rel_api_srv_cpm", FOREACH_COMPONENT_PREFIX, 
RELATION_SERVER_SUFFIX,
+            "metric_server_api_total_count.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_srv_dur", FOREACH_COMPONENT_PREFIX, 
RELATION_SERVER_SUFFIX,
+            "metric_server_api_total_duration.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_srv_suc", FOREACH_COMPONENT_PREFIX, 
RELATION_SERVER_SUFFIX,
+            "metric_server_api_http_success_count.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_srv_pct", FOREACH_COMPONENT_PREFIX, 
RELATION_SERVER_SUFFIX,
+            "metric_server_api_response_time_histogram" + HISTOGRAM_12_LABELS);
+    }
+
+    @Test
+    void serviceRelation6ArgWithForEachClient() throws Exception {
+        compileRule("rel_api_cli_cpm", FOREACH_COMPONENT_PREFIX, 
RELATION_CLIENT_SUFFIX,
+            "metric_client_api_total_count.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_cli_dur", FOREACH_COMPONENT_PREFIX, 
RELATION_CLIENT_SUFFIX,
+            "metric_client_api_total_duration.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_cli_suc", FOREACH_COMPONENT_PREFIX, 
RELATION_CLIENT_SUFFIX,
+            "metric_client_api_http_success_count.downsampling(SUM_PER_MIN)");
+        compileRule("rel_api_cli_pct", FOREACH_COMPONENT_PREFIX, 
RELATION_CLIENT_SUFFIX,
+            "metric_client_api_response_time_histogram" + HISTOGRAM_12_LABELS);
+    }
+
+    @Test
+    void serviceRelationTcpWithForEachServer() throws Exception {
+        for (final String metric : new String[]{
+                "write_bytes", "write_count", "write_execute_time",
+                "read_bytes", "read_count", "read_execute_time",
+                "connect_connection_count", "connect_connection_time",
+                "accept_connection_count", "accept_connection_time",
+                "close_connection_count", "close_connection_time"}) {
+            compileRule("rel_tcp_srv_" + metric, FOREACH_COMPONENT_PREFIX, 
RELATION_SERVER_SUFFIX,
+                "metric_server_tcp_" + metric + ".downsampling(SUM_PER_MIN)");
+        }
+    }
+
+    @Test
+    void serviceRelationTcpWithForEachClient() throws Exception {
+        for (final String metric : new String[]{
+                "write_bytes", "write_count", "write_execute_time",
+                "read_bytes", "read_count", "read_execute_time",
+                "connect_connection_count", "connect_connection_time",
+                "accept_connection_count", "accept_connection_time",
+                "close_connection_count", "close_connection_time"}) {
+            compileRule("rel_tcp_cli_" + metric, FOREACH_COMPONENT_PREFIX, 
RELATION_CLIENT_SUFFIX,
+                "metric_client_tcp_" + metric + ".downsampling(SUM_PER_MIN)");
+        }
+    }
+}
diff --git 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
index a850977a01..8bd2d4c179 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
@@ -26,6 +26,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+/**
+ * Basic compilation, error handling, source generation, and bytecode 
verification
+ * tests for MALClassGenerator.
+ */
 class MALClassGeneratorTest {
 
     private MALClassGenerator generator;
@@ -35,12 +39,13 @@ class MALClassGeneratorTest {
         generator = new MALClassGenerator(new ClassPool(true));
     }
 
+    // ==================== Basic compilation tests ====================
+
     @Test
     void compileSimpleMetric() throws Exception {
         final MalExpression expr = generator.compile(
             "test_metric", "instance_jvm_cpu");
         assertNotNull(expr);
-        // Run returns SampleFamily.EMPTY since no samples are provided
         assertNotNull(expr.run(java.util.Map.of()));
     }
 
@@ -110,9 +115,7 @@ class MALClassGeneratorTest {
         final String source = generator.generateSource(
             "instance_jvm_cpu.sum(['service'])");
         assertNotNull(source);
-        // Generated source should contain getOrDefault for the metric
-        org.junit.jupiter.api.Assertions.assertTrue(
-            source.contains("getOrDefault"));
+        assertTrue(source.contains("getOrDefault"));
     }
 
     @Test
@@ -149,260 +152,37 @@ class MALClassGeneratorTest {
 
     @Test
     void emptyExpressionThrows() {
-        // Demo error: MAL expression parsing failed: 1:0 mismatched input 
'<EOF>'
-        //   expecting {IDENTIFIER, NUMBER, '(', '-'}
         assertThrows(Exception.class, () -> generator.compile("test", ""));
     }
 
     @Test
     void malformedExpressionThrows() {
-        // Demo error: MAL expression parsing failed: 1:7 token recognition 
error at: '@'
         assertThrows(Exception.class,
             () -> generator.compile("test", "metric.@invalid"));
     }
 
     @Test
     void unclosedParenthesisThrows() {
-        // Demo error: MAL expression parsing failed: 1:8 mismatched input 
'<EOF>'
-        //   expecting {')', '+', '-', '*', '/'}
         assertThrows(Exception.class,
             () -> generator.compile("test", "(metric1 "));
     }
 
     @Test
     void invalidFilterClosureThrows() {
-        // Demo error: MAL filter parsing failed: 1:0 mismatched input 
'invalid'
-        //   expecting '{'
         assertThrows(Exception.class,
             () -> generator.compileFilter("invalid filter"));
     }
 
     @Test
     void emptyFilterBodyThrows() {
-        // Demo error: MAL filter parsing failed: 1:1 mismatched input '}'
-        //   expecting {IDENTIFIER, ...}
         assertThrows(Exception.class,
             () -> generator.compileFilter("{ }"));
     }
 
-    // ==================== Closure key extraction tests ====================
-
-    @Test
-    void tagClosurePutsCorrectKey() throws Exception {
-        // Issue: tags.cluster = expr should generate tags.put("cluster", ...)
-        // NOT tags.put("tags.cluster", ...)
-        final MalExpression expr = generator.compile(
-            "test_key",
-            "metric.tag({tags -> tags.cluster = 'activemq::' + 
tags.cluster})");
-        assertNotNull(expr);
-        final String source = generator.generateSource(
-            "metric.tag({tags -> tags.cluster = 'activemq::' + 
tags.cluster})");
-        assertTrue(source.contains("this._tag"),
-            "Generated source should reference pre-compiled closure");
-    }
-
-    @Test
-    void tagClosureKeyExtractionViaGeneratedCode() throws Exception {
-        // Verify the closure generates correct put("cluster", ...) not 
put("tags.cluster", ...)
-        final MalExpression expr = generator.compile(
-            "test_key_gen",
-            "metric.tag({tags -> tags.service_name = 'svc1'})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void tagClosureBracketAssignment() throws Exception {
-        // tags['key_name'] = 'value' should also use correct key
-        final MalExpression expr = generator.compile(
-            "test_bracket",
-            "metric.tag({tags -> tags['my_key'] = 'my_value'})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    // ==================== forEach closure tests ====================
-
-    @Test
-    void forEachClosureCompiles() throws Exception {
-        // forEach requires ForEachFunction.accept(String, Map), not 
TagFunction.apply(Map)
-        final MalExpression expr = generator.compile(
-            "test_foreach",
-            "metric.forEach(['client', 'server'], {prefix, tags ->"
-            + " tags[prefix + '_name'] = 'value'})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void forEachClosureWithBareReturn() throws Exception {
-        // forEach with bare return (void method) — should not throw
-        final MalExpression expr = generator.compile(
-            "test_foreach_return",
-            "metric.forEach(['x'], {prefix, tags ->\n"
-            + "  if (tags[prefix + '_id'] != null) {\n"
-            + "    return\n"
-            + "  }\n"
-            + "  tags[prefix + '_id'] = 'default'\n"
-            + "})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void forEachClosureWithVarDeclAndElseIf() throws Exception {
-        // Full pattern from network-profiling.yaml second closure
-        final MalExpression expr = generator.compile(
-            "test_foreach_vars",
-            "metric.forEach(['component'], {key, tags ->\n"
-            + "  String result = \"\"\n"
-            + "  String protocol = tags['protocol']\n"
-            + "  String ssl = tags['is_ssl']\n"
-            + "  if (protocol == 'http' && ssl == 'true') {\n"
-            + "    result = '129'\n"
-            + "  } else if (protocol == 'http') {\n"
-            + "    result = '49'\n"
-            + "  } else if (ssl == 'true') {\n"
-            + "    result = '130'\n"
-            + "  } else {\n"
-            + "    result = '110'\n"
-            + "  }\n"
-            + "  tags[key] = result\n"
-            + "})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    // ==================== ProcessRegistry FQCN resolution tests 
====================
-
-    @Test
-    void processRegistryResolvedToFQCN() throws Exception {
-        // ProcessRegistry.generateVirtualLocalProcess() should resolve to FQCN
-        final MalExpression expr = generator.compile(
-            "test_registry",
-            "metric.forEach(['client'], {prefix, tags ->\n"
-            + "  tags[prefix + '_process_id'] = "
-            + "ProcessRegistry.generateVirtualLocalProcess(tags.service, 
tags.instance)\n"
-            + "})");
-        assertNotNull(expr);
-        // We can't easily execute this (needs ProcessRegistry runtime) but 
compile should succeed
-    }
-
-    // ==================== Network-profiling full expression tests 
====================
-
-    @Test
-    void networkProfilingFirstClosureCompiles() throws Exception {
-        // Full first closure from network-profiling.yaml expPrefix
-        final MalExpression expr = generator.compile(
-            "test_np1",
-            "metric.forEach(['client', 'server'], { prefix, tags ->\n"
-            + "    if (tags[prefix + '_process_id'] != null) {\n"
-            + "      return\n"
-            + "    }\n"
-            + "    if (tags[prefix + '_local'] == 'true') {\n"
-            + "      tags[prefix + '_process_id'] = ProcessRegistry"
-            + ".generateVirtualLocalProcess(tags.service, tags.instance)\n"
-            + "      return\n"
-            + "    }\n"
-            + "    tags[prefix + '_process_id'] = ProcessRegistry"
-            + ".generateVirtualRemoteProcess(tags.service, tags.instance,"
-            + " tags[prefix + '_address'])\n"
-            + "  })");
-        assertNotNull(expr);
-    }
-
-    @Test
-    void networkProfilingSecondClosureCompiles() throws Exception {
-        // Full second closure from network-profiling.yaml expPrefix
-        final MalExpression expr = generator.compile(
-            "test_np2",
-            "metric.forEach(['component'], { key, tags ->\n"
-            + "    String result = \"\"\n"
-            + "    // protocol are defined in the component-libraries.yml\n"
-            + "    String protocol = tags['protocol']\n"
-            + "    String ssl = tags['is_ssl']\n"
-            + "    if (protocol == 'http' && ssl == 'true') {\n"
-            + "      result = '129'\n"
-            + "    } else if (protocol == 'http') {\n"
-            + "      result = '49'\n"
-            + "    } else if (ssl == 'true') {\n"
-            + "      result = '130'\n"
-            + "    } else {\n"
-            + "      result = '110'\n"
-            + "    }\n"
-            + "    tags[key] = result\n"
-            + "  })");
-        assertNotNull(expr);
-    }
-
-    // ==================== String concatenation in closures 
====================
-
-    @Test
-    void apisixExpressionCompiles() throws Exception {
-        // The APISIX expression that originally triggered the E2E failure:
-        // safe navigation + elvis + bracket access + string concat
-        final MalExpression expr = generator.compile(
-            "test_apisix",
-            "metric.tag({tags -> tags.service_name = 'APISIX::'"
-            + "+(tags['skywalking_service']?.trim()?:'APISIX')})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void closureStringConcatenation() throws Exception {
-        // APISIX-style: tags.service_name = 'APISIX::' + tags.service
-        final MalExpression expr = generator.compile(
-            "test_concat",
-            "metric.tag({tags -> tags.service_name = 'APISIX::' + 
tags.service})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void regexMatchWithDefCompiles() throws Exception {
-        // envoy-ca pattern: def + regex match + ternary with chained indexing
-        final MalExpression expr = generator.compile(
-            "test_regex",
-            "metric.tag({tags ->\n"
-            + "  def matcher = (tags.metrics_name =~ 
/\\.ssl\\.certificate\\.([^.]+)\\.expiration/)\n"
-            + "  tags.secret_name = matcher ? matcher[0][1] : \"unknown\"\n"
-            + "})");
-        assertNotNull(expr);
-        assertNotNull(expr.run(java.util.Map.of()));
-    }
-
-    @Test
-    void envoyCAExpressionCompiles() throws Exception {
-        // Full envoy-ca.yaml expression with regex closure, subtraction of 
time(), and service
-        final MalExpression expr = generator.compile(
-            "test_envoy_ca",
-            "(metric.tagMatch('metrics_name', 
'.*ssl.*expiration_unix_time_seconds')"
-            + ".tag({tags ->\n"
-            + "  def matcher = (tags.metrics_name =~ 
/\\.ssl\\.certificate\\.([^.]+)"
-            + "\\.expiration_unix_time_seconds/)\n"
-            + "  tags.secret_name = matcher ? matcher[0][1] : \"unknown\"\n"
-            + "}).min(['app', 'secret_name']) - time())"
-            + ".downsampling(MIN).service(['app'], Layer.MESH_DP)");
-        assertNotNull(expr);
-    }
-
-    @Test
-    void timeScalarFunctionHandledInMetadata() throws Exception {
-        // time() should not appear as a sample name and should be treated as 
scalar
-        final MalExpression expr = generator.compile(
-            "test_time",
-            "(metric.sum(['app']) - time()).service(['app'], Layer.GENERAL)");
-        assertNotNull(expr);
-        assertNotNull(expr.metadata());
-        // time() should not be in sample names
-        assertTrue(expr.metadata().getSamples().contains("metric"));
-        assertTrue(expr.metadata().getSamples().size() == 1);
-    }
+    // ==================== Bytecode verification ====================
 
     @Test
     void runMethodHasLocalVariableTable() throws Exception {
-        // Compile a class that writes its .class file for inspection
         final java.io.File tmpDir = 
java.nio.file.Files.createTempDirectory("mal-lvt").toFile();
         try {
             final ClassPool pool = new ClassPool(true);
@@ -411,11 +191,9 @@ class MALClassGeneratorTest {
             final MalExpression expr = gen.compile(
                 "test_lvt", "instance_jvm_cpu.sum(['service', 'instance'])");
             assertNotNull(expr);
-            // Read the .class file bytecode and verify LVT
             final java.io.File[] classFiles = tmpDir.listFiles((d, n) -> 
n.endsWith(".class"));
             assertNotNull(classFiles);
             assertTrue(classFiles.length > 0, "Should have generated .class 
file");
-            // Use javassist to read back and check for LocalVariableTable
             final javassist.bytecode.ClassFile cf =
                 new javassist.bytecode.ClassFile(
                     new java.io.DataInputStream(
@@ -428,7 +206,6 @@ class MALClassGeneratorTest {
                 (javassist.bytecode.LocalVariableAttribute)
                     
code.getAttribute(javassist.bytecode.LocalVariableAttribute.tag);
             assertNotNull(lva, "run() should have LocalVariableTable 
attribute");
-            // Check that slot 1 has name "samples"
             boolean foundSamples = false;
             boolean foundSf = false;
             for (int i = 0; i < lva.tableLength(); i++) {
@@ -449,5 +226,4 @@ class MALClassGeneratorTest {
             tmpDir.delete();
         }
     }
-
 }


Reply via email to