tests: закрыты три дыры в покрытии, найденные мутационным анализом
This commit is contained in:
+17
-2
@@ -42,19 +42,31 @@ def main() -> int:
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
f, old, new, must_fail = line.split("\t")
|
||||
rows.append((f, old.replace("\\n", "\n"), new.replace("\\n", "\n"), must_fail))
|
||||
old = old.replace("\\n", "\n")
|
||||
new = new.replace("\\n", "\n")
|
||||
# no-op мутация ничего не проверяет — это ошибка таблицы, а не дыра в покрытии.
|
||||
if old == new:
|
||||
print(f"ОШИБКА ТАБЛИЦЫ: no-op мутация для {must_fail} (old == new)")
|
||||
return 2
|
||||
rows.append((f, old, new, must_fail))
|
||||
|
||||
print("=== базовая линия (без мутаций) ===")
|
||||
base = run_tests(a.gradle)
|
||||
print(f"тестов найдено: {len(base)}; упавших: {[k for k, v in base.items() if v == 'FAIL']}")
|
||||
|
||||
survivors = []
|
||||
misapplied = 0
|
||||
for f, old, new, must_fail in rows:
|
||||
backup = f + ".bak"
|
||||
shutil.copy2(f, backup)
|
||||
try:
|
||||
src = open(f, encoding="utf-8").read()
|
||||
assert old in src, f"МУТАЦИЯ НЕ ПРИМЕНИЛАСЬ (нет фрагмента) в {f}: {old[:60]!r}"
|
||||
if old not in src:
|
||||
# Мутация не применилась: фрагмента нет в файле. Это ошибка таблицы,
|
||||
# а не доказательство покрытия — такой прогон ничего не значит.
|
||||
print(f"ОШИБКА ТАБЛИЦЫ | мутация не применилась в {f}: {old[:70]!r}")
|
||||
misapplied += 1
|
||||
continue
|
||||
open(f, "w", encoding="utf-8").write(src.replace(old, new, 1))
|
||||
st = run_tests(a.gradle)
|
||||
got = st.get(must_fail, "НЕ НАЙДЕН")
|
||||
@@ -74,6 +86,9 @@ def main() -> int:
|
||||
for t, f, old, new in survivors:
|
||||
print(f" - {t}: {f} `{old[:60]}` -> `{new[:40]}`")
|
||||
return 1
|
||||
if misapplied:
|
||||
print(f"\nПРОВАЛ: {misapplied} мутаций не применились — прогон недействителен")
|
||||
return 2
|
||||
print("\nВСЕ МУТАЦИИ УБИТЫ")
|
||||
return 0
|
||||
|
||||
|
||||
+17
-2
@@ -1,9 +1,24 @@
|
||||
# file<TAB>old<TAB>new<TAB>must_fail_test
|
||||
# Прогон: python3 e2e/mutation_check.py --gradle ./gradlew
|
||||
# Правило: мутация обязана уронить ровно тот тест, который её ловит. Выжившая мутация = дыра в покрытии.
|
||||
# no-op мутации (old == new) запрещены — харнесс их отклоняет.
|
||||
# --- memo-core: ksqlite / FTS5 / vec0 ---
|
||||
memo-core/src/test/kotlin/memo/core/CoreSmokeTest.kt CREATE VIRTUAL TABLE v USING vec0(embedding float[4]) CREATE VIRTUAL TABLE v USING vec0(embedding float[8]) vec0KnnRoundTrip
|
||||
memo-core/src/test/kotlin/memo/core/CoreSmokeTest.kt floatArrayOf(0.1f, 0.9f, 0.0f, 0.0f) floatArrayOf(0.0f, 0.0f, 0.0f, 1.0f) vec0KnnRoundTrip
|
||||
memo-core/src/test/kotlin/memo/core/CoreSmokeTest.kt tokenize='unicode61' tokenize='ascii' fts5FindsCyrillic
|
||||
memo-core/src/test/kotlin/memo/core/CoreSmokeTest.kt "SELECT a FROM t" "SELECT a+1 FROM t" ksqliteSmoke
|
||||
# --- Чанкер ---
|
||||
memo-core/src/main/kotlin/memo/core/Chunker.kt Regex("^(#{1,3}) (.*)$") Regex("^(#+) (.*)$") fourHashesIsNotAHeading
|
||||
memo-core/src/main/kotlin/memo/core/Chunker.kt val trimmed = body.trim() val trimmed = body fourHashesIsNotAHeading
|
||||
memo-core/src/main/kotlin/memo/core/Chunker.kt flush("", 1, preamble.toString())\n foundHeading = true flush("", 1, preamble.toString())\n foundHeading = true // x textBeforeFirstHeadingBecomesChunkWithEmptyHeading
|
||||
memo-core/src/main/kotlin/memo/core/Chunker.kt val trimmed = body.trim() val trimmed = body.uppercase() fourHashesIsNotAHeading
|
||||
memo-core/src/main/kotlin/memo/core/Chunker.kt private const val CHUNK_OVERLAP = 600 private const val CHUNK_OVERLAP = 0 longSectionIsSplitWithOverlap
|
||||
# --- Коллекции (дефект тройного обхода, найден на приёмке) ---
|
||||
memo-core/src/main/kotlin/memo/core/Collections.kt it.isFile && it.extension == "md" it.name.endsWith("md") findCollectionsReturnsOnlyDirsWithDirectMarkdown
|
||||
# --- Индексатор: пропуск неизменённого файла ---
|
||||
memo-core/src/main/kotlin/memo/core/Indexer.kt if (existing.mtime == mtime && existing.size == size) {\n return false if (existing.mtime == mtime && existing.size == size) {\n return true indexerSkipsUnchangedFile
|
||||
# --- Поиск: слияние режимов и ограничение k ---
|
||||
memo-core/src/main/kotlin/memo/core/Searcher.kt if (mode == SearchMode.LEX || mode == SearchMode.HYBRID) lexSearch(query) else emptyList() if (mode == SearchMode.LEX) lexSearch(query) else emptyList() hybridCombinesBoth
|
||||
memo-core/src/main/kotlin/memo/core/Searcher.kt .sortedByDescending { it.value }.take(k) .sortedByDescending { it.value }.take(k + 1) resultsRespectKAndTextLength
|
||||
# --- MCP: ошибка инструмента обязана помечаться isError ---
|
||||
memo-mcp/src/main/kotlin/memo/mcp/McpServer.kt ],"isError":true} ],"isError":false} searchWithoutPathIsError
|
||||
# --- Watcher: debounce сводит правки к одному вызову ---
|
||||
memo-watch/src/main/kotlin/memo/watch/Watcher.kt _indexCalls.incrementAndGet() _indexCalls.get() modifyTriggersSingleIndexCall
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 54.
|
Reference in New Issue
Block a user