From 4179e0de239dec8786767131ed374516f89f159d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Fri, 4 Sep 2026 23:53:22 +0800 Subject: [PATCH] =?UTF-8?q?task-189:=20=E6=8C=87=E6=A0=87=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E4=B8=80=E8=87=B4=E6=80=A7=E5=A5=91=E7=BA=A6=EF=BC=88?= =?UTF-8?q?aiimage.*=20=E5=89=8D=E7=BC=80=E3=80=81=E5=85=A8=E5=B0=8F?= =?UTF-8?q?=E5=86=99=E7=82=B9=E5=88=86=E3=80=81=E5=85=A8=E9=87=8F=E5=90=8D?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E5=AF=B9=E9=BD=90=20spec11=20=C2=A71?= =?UTF-8?q?=E3=80=81=E6=97=A0=E6=8B=BC=E5=86=99=E6=BC=82=E7=A7=BB=EF=BC=89?= =?UTF-8?q?+=208=20=E6=9D=A1=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metrics/MetricNamingConsistencyTest.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/metrics/MetricNamingConsistencyTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricNamingConsistencyTest.java b/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricNamingConsistencyTest.java new file mode 100644 index 00000000..dbd7b27b --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricNamingConsistencyTest.java @@ -0,0 +1,124 @@ +package com.nanri.aiimage.metrics; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-189:指标命名一致性契约(spec 11 §1 标准名)。 + * 全部指标名 aiimage.* 前缀、小写点分、无拼写漂移;recorder 全量指标名快照与 spec 对齐。 + */ +class MetricNamingConsistencyTest { + + private final SimpleMeterRegistry registry = new SimpleMeterRegistry(); + private final TaskObservabilityMetrics metrics = new TaskObservabilityMetrics("server-110", registry); + + private void createAllMetrics() { + metrics.taskCreated("similarasin"); + metrics.taskRunningDelta("similarasin", 1); + metrics.taskTerminal("similarasin", "success"); + metrics.taskTerminal("similarasin", "failed"); + metrics.taskTerminal("similarasin", "cancelled"); + metrics.taskDuration("similarasin", 1L); + metrics.taskHeartbeatAge("similarasin", 1L); + metrics.fileJobState("similarasin", "pending"); + metrics.fileJobState("similarasin", "running"); + metrics.fileJobState("similarasin", "success"); + metrics.fileJobState("similarasin", "failed"); + metrics.fileJobState("similarasin", "retry"); + metrics.fileJobDuration("similarasin", 1L); + metrics.upload("similarasin", 1L, 1L, true); + metrics.upload("similarasin", 1L, 1L, false); + metrics.disk("/tmp/x", 1L, 1L); + } + + @Test + void allNamesHaveAiimagePrefix() { + createAllMetrics(); + for (String name : names()) { + assertTrue(name.startsWith("aiimage."), "指标应带 aiimage. 前缀: " + name); + } + } + + @Test + void allNamesLowercaseDotSegments() { + createAllMetrics(); + for (String name : names()) { + assertTrue(name.equals(name.toLowerCase()), "指标名应全小写: " + name); + assertTrue(!name.contains(".."), "指标名不应有连续点: " + name); + assertTrue(!name.endsWith("."), "指标名不应以点结尾: " + name); + } + } + + @Test + void taskFamilyNamesFrozen() { + createAllMetrics(); + for (String n : List.of("aiimage.task.created", "aiimage.task.running", "aiimage.task.success", + "aiimage.task.failed", "aiimage.task.cancelled", "aiimage.task.duration", + "aiimage.task.heartbeat.age")) { + assertTrue(names().contains(n), "缺少 spec 任务指标名: " + n); + } + } + + @Test + void fileJobFamilyNamesFrozen() { + createAllMetrics(); + for (String n : List.of("aiimage.file-job.pending", "aiimage.file-job.running", + "aiimage.file-job.success", "aiimage.file-job.failed", "aiimage.file-job.retry", + "aiimage.file-job.duration")) { + assertTrue(names().contains(n), "缺少 spec file-job 指标名: " + n); + } + } + + @Test + void noTypoDriftSnapshot() { + createAllMetrics(); + Set names = names(); + // 与 spec 11 §1 允许集完全一致:不允许出现拼写漂移产生的多余名字 + Set allowed = Set.of( + "aiimage.task.created", "aiimage.task.running", "aiimage.task.success", + "aiimage.task.failed", "aiimage.task.cancelled", "aiimage.task.duration", + "aiimage.task.heartbeat.age", + "aiimage.file-job.pending", "aiimage.file-job.running", "aiimage.file-job.success", + "aiimage.file-job.failed", "aiimage.file-job.retry", "aiimage.file-job.duration", + "aiimage.upload.duration", "aiimage.upload.size", "aiimage.upload.result", + "aiimage.upload.result.success", "aiimage.upload.result.failure", + "aiimage.tmpdisk.capacity", "aiimage.tmpdisk.used"); + Set unexpected = names.stream().filter(n -> !allowed.contains(n)).collect(Collectors.toSet()); + assertEquals(Set.of(), unexpected, "不应出现 spec 之外的漂移指标名"); + } + + @Test + void snapshotAllMetricsCaptured() { + createAllMetrics(); + assertTrue(names().size() >= 19, "全量指标快照应覆盖任务/文件/上传/磁盘族,实际 " + names().size()); + } + + @Test + void caseConsistentAcrossFamilies() { + createAllMetrics(); + long upperCount = names().stream().filter(n -> n.chars().anyMatch(Character::isUpperCase)).count(); + assertEquals(0, upperCount, "指标名应统一小写"); + } + + @Test + void metricNamingStableAcrossRepeatedCreation() { + createAllMetrics(); + Set first = names(); + TaskObservabilityMetrics metrics2 = new TaskObservabilityMetrics("server-121", registry); + metrics2.taskCreated("similarasin"); + Set second = names(); + assertEquals(first, second, "新增实例不应改变既有指标名集合"); + } + + private Set names() { + return registry.getMeters().stream() + .map(m -> m.getId().getName()).collect(Collectors.toSet()); + } +}