From fef3eb28dda37e218adf2c229b9f1c5bd0780098 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Tue, 29 Sep 2026 09:38:47 +0900 Subject: [PATCH] =?UTF-8?q?test:=20Python=20=EB=8C=80=EC=A1=B0=EC=9A=A9=20?= =?UTF-8?q?=EB=8D=A4=ED=94=84=20=EC=8B=9C=ED=97=98=203=EA=B1=B4=EC=9D=98?= =?UTF-8?q?=20=EC=9E=85=C2=B7=EC=B6=9C=EB=A0=A5=EC=9D=84=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B0=9B=EA=B3=A0,?= =?UTF-8?q?=20=EC=97=86=EC=9C=BC=EB=A9=B4=20=EA=B1=B4=EB=84=88=EB=9B=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cm3PerTraceDump·GoldenDump·PrecisionDump 는 실측 세션 JSON 을 파이프라인에 흘려 중간값을 떨구는 개발 도구인데, 입력이 이 PC 의 절대 경로, 출력이 옛 Claude 세션의 임시 폴더로 박혀 있었다. 그 폴더가 사라진 뒤로 전체 단위시험이 늘 3건 실패였고, 다른 PC·CI 에서는 입력부터 없다. DumpEnv: VESISCAN_DATA123_DIR(입력 폴더) · VESISCAN_DUMP_DIR(출력 폴더). 둘 다 없으면 Assume 로 건너뛴다 — 보고서에 skipped 로 남아 "통과"로 위장하지 않는다. 확인: 기본 실행 168건 중 skipped 3 · failures 0. 환경 변수를 주고 돌리면 3건 모두 실행되어 cm3/golden/precision_kotlin.json 을 쓴다. Co-Authored-By: Claude Fable 5.1 --- .../vesiscan/managers/Cm3PerTraceDumpTest.kt | 7 ++- .../medithings/vesiscan/managers/DumpEnv.kt | 53 +++++++++++++++++++ .../vesiscan/managers/GoldenDumpTest.kt | 19 ++++--- .../vesiscan/managers/PrecisionDumpTest.kt | 7 ++- 4 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 app/src/test/java/com/medithings/vesiscan/managers/DumpEnv.kt diff --git a/app/src/test/java/com/medithings/vesiscan/managers/Cm3PerTraceDumpTest.kt b/app/src/test/java/com/medithings/vesiscan/managers/Cm3PerTraceDumpTest.kt index b068d8d..452aad4 100644 --- a/app/src/test/java/com/medithings/vesiscan/managers/Cm3PerTraceDumpTest.kt +++ b/app/src/test/java/com/medithings/vesiscan/managers/Cm3PerTraceDumpTest.kt @@ -11,11 +11,12 @@ import kotlin.math.sqrt /** * cm=3 세션의 11 trace 각각 BV + 중간값을 dump 해서 Python (cm3_python.json) 과 대조. * divergent trace 특정용. + * + * 입·출력 위치는 [DumpEnv] — 환경 변수가 없으면 건너뛴다(개발 도구이지 회귀 시험이 아니다). */ class Cm3PerTraceDumpTest { - private val session = "c:/Projects/vesiscan-design-archive/data123/dwjang_HUMAN-kai_VBT26040302_SUPINE_ALIGN_3CM_2026-07-06_164842.json" - private val outPath = "C:/Users/장동우/AppData/Local/Temp/claude/c--Projects-medilightv2android/76311e3b-2bb5-4c52-ab8a-8d89cb1052c2/scratchpad/cm3_kotlin.json" + private val sessionName = "dwjang_HUMAN-kai_VBT26040302_SUPINE_ALIGN_3CM_2026-07-06_164842.json" private fun loadCycles(path: String, maxN: Int = 20): List> { val root = JsonParser.parseReader(File(path).bufferedReader()).asJsonObject @@ -54,6 +55,8 @@ class Cm3PerTraceDumpTest { @Test fun `dump cm=3 per-trace BV for python comparison`() { + val session = DumpEnv.dataFileOrSkip(sessionName).path + val outPath = DumpEnv.outFileOrSkip("cm3_kotlin.json").path PiezoHW.activePreset = PiezoHW.DevicePreset.V1 val cycles = loadCycles(session, 20) val params = MethodDParams.DEFAULT diff --git a/app/src/test/java/com/medithings/vesiscan/managers/DumpEnv.kt b/app/src/test/java/com/medithings/vesiscan/managers/DumpEnv.kt new file mode 100644 index 0000000..f512129 --- /dev/null +++ b/app/src/test/java/com/medithings/vesiscan/managers/DumpEnv.kt @@ -0,0 +1,53 @@ +/* + * DumpEnv — Python 대조용 덤프 시험 3개(Cm3PerTraceDump · GoldenDump · PrecisionDump)의 + * 입·출력 위치. + * + * 이 셋은 "시험"이라기보다 **개발 도구**다: 실측 세션 JSON 을 파이프라인에 흘려 중간값을 + * JSON 으로 떨구고 Python 결과와 diff 한다. 2026-09-29 까지는 입력이 이 PC 의 절대 경로, + * 출력이 옛 Claude 세션의 임시 폴더로 박혀 있어, 그 폴더가 사라진 뒤로 전체 단위시험이 + * 늘 3건 실패였다 — 다른 PC·CI 에서는 입력부터 없다. + * + * 그래서 둘 다 환경 변수로 받고, 없으면 **건너뛴다**(JUnit Assume → skipped). 일부러 돌릴 때만: + * + * VESISCAN_DATA123_DIR=c:/Projects/vesiscan-design-archive/data123 \ + * VESISCAN_DUMP_DIR=<출력 폴더> \ + * ./gradlew :app:testDemoDebugUnitTest --tests '*DumpTest*' + * + * 건너뛴 것은 gradle 보고서에 skipped 로 남는다 — "통과"로 위장하지 않는다. + */ +package com.medithings.vesiscan.managers + +import org.junit.Assume.assumeTrue +import java.io.File + +object DumpEnv { + + const val DATA_ENV = "VESISCAN_DATA123_DIR" + const val OUT_ENV = "VESISCAN_DUMP_DIR" + + /** 실측 세션 JSON 폴더. 환경 변수가 없거나 폴더가 없으면 건너뛴다. */ + fun dataDirOrSkip(): File { + val v = System.getenv(DATA_ENV) + assumeTrue("$DATA_ENV 가 없어 덤프 시험을 건너뛴다", !v.isNullOrBlank()) + val dir = File(v!!) + assumeTrue("$DATA_ENV=$v 폴더가 없어 덤프 시험을 건너뛴다", dir.isDirectory) + return dir + } + + /** [dataDirOrSkip] 안의 세션 파일. 그 파일이 없어도 건너뛴다. */ + fun dataFileOrSkip(name: String): File { + val f = File(dataDirOrSkip(), name) + assumeTrue("세션 파일이 없어 덤프 시험을 건너뛴다: ${f.path}", f.isFile) + return f + } + + /** 덤프 출력 파일. 환경 변수가 없으면 건너뛴다. 폴더는 만들어 둔다. */ + fun outFileOrSkip(name: String): File { + val v = System.getenv(OUT_ENV) + assumeTrue("$OUT_ENV 가 없어 덤프 시험을 건너뛴다", !v.isNullOrBlank()) + val dir = File(v!!) + dir.mkdirs() + assumeTrue("$OUT_ENV=$v 폴더를 만들 수 없어 덤프 시험을 건너뛴다", dir.isDirectory) + return File(dir, name) + } +} diff --git a/app/src/test/java/com/medithings/vesiscan/managers/GoldenDumpTest.kt b/app/src/test/java/com/medithings/vesiscan/managers/GoldenDumpTest.kt index d666b30..80addba 100644 --- a/app/src/test/java/com/medithings/vesiscan/managers/GoldenDumpTest.kt +++ b/app/src/test/java/com/medithings/vesiscan/managers/GoldenDumpTest.kt @@ -13,17 +13,20 @@ import java.io.File * * 실행: * 1. Python golden 생성: python scratchpad/golden_dump_python.py - * 2. Kotlin dump: ./gradlew testDevDebugUnitTest --tests "*GoldenDumpTest*" + * 2. Kotlin dump: VESISCAN_DATA123_DIR=… VESISCAN_DUMP_DIR=… \ + * ./gradlew :app:testDemoDebugUnitTest --tests "*GoldenDumpTest*" * 3. diff : python scratchpad/golden_diff.py + * + * 입·출력 위치는 [DumpEnv] — 환경 변수가 없으면 건너뛴다(개발 도구이지 회귀 시험이 아니다). */ class GoldenDumpTest { + /** [DumpEnv.dataDirOrSkip] 안의 파일명. */ private val sessions = mapOf( - "cm0" to "c:/Projects/vesiscan-design-archive/data123/dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_0CM_2026-07-06_164150.json", - "cm1" to "c:/Projects/vesiscan-design-archive/data123/dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_1CM_2026-07-06_164231.json", - "cm3" to "c:/Projects/vesiscan-design-archive/data123/dwjang_HUMAN-kai_VBT26040302_SUPINE_ALIGN_3CM_2026-07-06_164842.json", + "cm0" to "dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_0CM_2026-07-06_164150.json", + "cm1" to "dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_1CM_2026-07-06_164231.json", + "cm3" to "dwjang_HUMAN-kai_VBT26040302_SUPINE_ALIGN_3CM_2026-07-06_164842.json", ) - private val outPath = "C:/Users/장동우/AppData/Local/Temp/claude/c--Projects-medilightv2android/76311e3b-2bb5-4c52-ab8a-8d89cb1052c2/scratchpad/golden_kotlin.json" private fun loadCycles(path: String, maxN: Int = 20): List> { val root = JsonParser.parseReader(File(path).bufferedReader()).asJsonObject @@ -115,11 +118,13 @@ class GoldenDumpTest { @Test fun `golden dump all sessions`() { + val dataDir = DumpEnv.dataDirOrSkip() + val outPath = DumpEnv.outFileOrSkip("golden_kotlin.json").path PiezoHW.activePreset = PiezoHW.DevicePreset.V1 val cases = mutableListOf>() val win = 10 - for ((sid, path) in sessions) { - val cycles = loadCycles(path, 20) + for ((sid, name) in sessions) { + val cycles = loadCycles(File(dataDir, name).path, 20) if (cycles.size < win) { println(" skip $sid: only ${cycles.size} cycles") continue diff --git a/app/src/test/java/com/medithings/vesiscan/managers/PrecisionDumpTest.kt b/app/src/test/java/com/medithings/vesiscan/managers/PrecisionDumpTest.kt index faf790e..190c731 100644 --- a/app/src/test/java/com/medithings/vesiscan/managers/PrecisionDumpTest.kt +++ b/app/src/test/java/com/medithings/vesiscan/managers/PrecisionDumpTest.kt @@ -15,11 +15,12 @@ import java.io.File * * cm=1 세션 첫 10 cycle 평균 (trace 0) 을 pipeline 각 단계에 흘리면서 결과를 JSON 으로 저장. * scratchpad/precision_python.json 과 비교해 divergence 지점 특정. + * + * 입·출력 위치는 [DumpEnv] — 환경 변수가 없으면 건너뛴다(개발 도구이지 회귀 시험이 아니다). */ class PrecisionDumpTest { - private val session = "c:/Projects/vesiscan-design-archive/data123/dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_1CM_2026-07-06_164231.json" - private val outPath = "C:/Users/장동우/AppData/Local/Temp/claude/c--Projects-medilightv2android/76311e3b-2bb5-4c52-ab8a-8d89cb1052c2/scratchpad/precision_kotlin.json" + private val sessionName = "dwjang_HUMAN-kai_VBT26050202_SUPINE_ALIGN_1CM_2026-07-06_164231.json" private fun loadFirstTrace(path: String, win: Int = 10): List { val root = JsonParser.parseReader(File(path).bufferedReader()).asJsonObject @@ -55,6 +56,8 @@ class PrecisionDumpTest { @Test fun `dump pipeline intermediates for cm=1 trace 0`() { + val session = DumpEnv.dataFileOrSkip(sessionName).path + val outPath = DumpEnv.outFileOrSkip("precision_kotlin.json").path PiezoHW.activePreset = PiezoHW.DevicePreset.V1 EllipseFitSpecific.debugForceEigenvector = null val trace = loadFirstTrace(session, 10)