Çıkış Koduna Bakan Bir Mutasyon Turu, Hiç Başlamamış Test Paketini Öldürme Sayar
Test çalıştırıcısının çıkış koduna güvenen bir mutasyon testi aracı, hiç başlamamış bir test paketini öldürülmüş mutant olarak sayar. Ephesus bu hataya iki kez düştü. Depoya eklenen yeni araç artık vitest'in JSON raporunu okuyor, yalnızca baseline'da geçtiği kanıtlanmış testleri sayıyor ve hak etmediği hiçbir hükmü vermiyor.
Başlamayı reddeden test paketi de 1 koduyla çıkar
Ephesus, kod yazan yapay zekâ ajanlarını sahibinin koyduğu gate'lerin arkasında çalıştıran, pre-alpha aşamasında bir Electron uygulaması. Sekizinci kilometre taşından beri doğrulama çıtasının bir ayağı mutasyon turu: bir paketin savunduğu koda elle hedeflenmiş tek bir değişiklik yerleştirilir, paketin testleri çalıştırılır, dosya geri konur.
Test paketinin bir boş bellek kapısı var. test/global-setup.ts, boş bellek 1 GB'ın altındaysa başlamayı reddediyor, çünkü proje bu seviyenin altında vitest fork'larının çalışma ortasında öldüğünü gözlemledi. Reddettiğinde tek bir test çalıştırmadan 1 koduyla çıkıyor. 9 Eylül 2026'da boş bellek 0,90 GB'tayken bir tur 11/12 skoru bildirdi ve içindeki hükümlerin hiçbiri hak edilmiş değildi (karar günlüğü).
2 Ekim'de sıfırdan yazılmış geçici bir betik aynı hatayı tekrarladı: 28 koşunun 14'ü hiç test çalıştırmadı ama öldürme olarak raporlandı ve bunların üçü kontrol mutantıydı (uygulama kaydı). O güne kadar hiçbir tur depoda duran bir araçla koşmamıştı. Tek bir kilometre taşı 11 tur koşmuştu ve her biri, oturumun geçici klasörüne yazılıp oturumla birlikte silinen bir betikle yürütülmüştü (GYM-008 önerisi). Her düzeltme hafızaya emanetti ve hatalar geri döndü.
Kontrol mutantı turun sertifikasıdır
Kontrol mutantı, hiçbir testin göremeyeceği bir değişikliktir, örneğin yeniden yazılmış bir doc comment; hayatta kalmak zorundadır. 9 Eylül turunda kontrol öldürülmüş olarak döndü, yani mutantları testlerin dışında bir şey öldürmüştü. 2 Ekim'deki sahte öldürmelerden üçü kontrol mutantıydı. mutate.cjs artık kontrol tanımlamayan bir spec'i reddediyor; kontrol öldürülürse turun tamamını INVALID ilan edip 2 koduyla çıkıyor.
Aynı mantık hayatta kalmaları da denetliyor. Hiçbir yerle eşleşmeyen bir anchor, mutantı "hayatta kalan" bir no-op'a çevirir; iki yerle eşleşen anchor ise kimsenin seçmediği bir düzenleme yapar. Araç eşleşmeleri bayt düzeyinde, üst üste binenler dahil sayıyor ve baseline başlamadan önce sayısı tam olarak bir olmayan her anchor'ı reddediyor. Öneri ilkeyi tek cümlede veriyor: hak edilmemiş bir öldürme ile hak edilmemiş bir hayatta kalma aynı hatadır.
mutate.cjs mutantları elle hedefliyor. GYM-008, çoğunlukla eşdeğer mutant ürettiğini söylediği Stryker gibi üreteçleri reddetti. Tur kaynak dosyalarını yeniden yazdığı için bir CI işi olmayı da reddetti.
Çıkış kodu 1, hem öldürmeyi hem hiçbir şey kanıtlamayan koşuyu kapsar
Her sonucu okuyan kural yazılmadan önce o sonuç, vitest 4.1.11 bir fixture üzerinde çalıştırılarak üretildi ve raporun biçimi kayda geçti (kayıt, bölüm 3.2). Bu biçimlerden beşi, iki sinyalin de tek başına neden yetmediğini gösteriyor:
| Ne oldu | vitest geride ne bıraktı | Hüküm |
|---|---|---|
| baseline'da geçen bir test başarısız oldu | o test failed, çıkış 1 | öldürme |
| testin içe aktardığı modül artık ayrıştırılamıyor | dosya failed, içinde test yok, çıkış 1 | öldürme, dosya öldürmesi olarak raporlanır |
| global setup hata fırlattı, bellek kapısının yaptığı gibi | hiç dosya içermeyen bir rapor, çıkış 1 | INVALID, yeniden denenir |
| test edilen kod worker'ı öldürdü | success: true, testi pending, çıkış 1 | INVALID |
| yakalanmamış bir promise reddi (unhandled rejection) | success: true, tüm testler passed, çıkış 1 | INVALID |
Bellek kapısı için çıkış kodu 1 diyor, ölü bir worker için de rapor success diyor. Bu yüzden classify() önce raporu, sonra çıkış kodunu, en son da baseline'da geçen testlerin kümesini okuyor. Yalnızca başlamayı reddeden koşu yeniden deneniyor, varsayılan olarak iki kez ve her seferinde 30 saniye beklenerek. Varsayılan 900 saniyelik zaman aşımına takılan koşu ise INVALID sayılıyor ve yeniden denenmiyor.
Öldürme sayılması için baseline'da geçmiş bir testin düşmesi gerekir
Öldürme kuralı en son, ikinci düşmanca incelemeden sonra değişti. b20f83b commit'ine kadar başarısız olan her test sayılıyordu; mutantın etkinleştirdiği ve hiç geçmemiş bir test de buna dahildi. Artık baseline hangi testlerin geçtiğini dosya, tam ad ve aynı adlı testler arasındaki sıra ile kaydediyor. Bir mutant koşusu ancak bunlardan biri başarısız olursa ya da listelenen bir dosya hiç yüklenemezse öldürme sayılıyor; geri kalan her başarısızlık INVALID.
Aynı baseline kümesi daha önceki iki açığı da kapatıyor. Testlerinin hepsi skip edilmiş bir dosya boşu boşuna geçerdi, bu yüzden listelenen her dosyada baseline'da geçmiş en az bir test bulunmalı. Ölen bir worker testleri pending bırakır, bu yüzden her baseline testi her mutantta ya geçmeli ya da başarısız olmalı.
Daha katı seçenek, skip, todo ya da .only ile işaretli her testi doğrudan reddetmekti ve reddedildi: test paketindeki platforma bağlı skip'ler o dosyaları hiçbir tura sokulamaz hale getirirdi. Testleri adla anahtarlamanın bedeli TEST-STRATEGY bölüm 10 içinde bilinen bir sınır olarak yazılı: aynı adlı bir test, baseline'da geçen testin yerine geçebiliyor.
Araç kendi turunu geçti, ardından düşmanca bir inceleme onu kırdı
mutate.cjs, kendisini hedef alan bir spec ile birlikte geliyor. İlk tur bir hayatta kalan buldu: izlenen dosya kontrolü silindiğinde .gitignore'un gizlediği bir tur dosyasını hiçbir şey reddetmiyordu. Eksik bir test vardı ve yazıldı. İkinci tur temiz geçti.
Ardından bağımsız bir inceleme o sürüme 18 fixture turu boyunca 22 saldırı denedi ve aracın hak etmediği hükümler vermesini sağladı. describe.skip içeren bir dosya önce yeşil bir baseline, ardından bir hayatta kalan üretti. Bir assume-unchanged bayrağı, geri yüklemenin commit edilmemiş işi silmesine yol açtı. Her bulgu, düzeltmesi yazılmadan önce commit edilmiş araca karşı yeniden üretildi.
Her tur dosyası her koşudan ve her geri yüklemeden sonra yeniden hash'leniyor, çünkü OneDrive iki kez geri yüklenmiş bir dosyayı içinde mutantla birlikte geri koydu. Bu, git'in arka plana bıraktığı bakımın Ephesus CI'ında yarattığı türden geç bir yazma.
İkinci bir inceleme on bir yol daha buldu. Bunlar kapatılmadı, #70 numaralı issue altında bilinen sınırlar olarak kayda geçti; örneğin vitest'in retry ayarı bir öldürmeyi geçişe çevirebiliyor. Son kod üzerindeki dördüncü tur, kontrol hayatta kalırken 43 gerçek mutantın 43'ünü öldürdü ve Windows'ta 57 dk 55 sn sürdü (PR #71).
Her mutasyon aracına kopyalanmaya değer üç kural
- Her tura bir no-op kontrol yerleştirin ve kontrol ölürse turu geçersiz sayın.
- Yapılandırılmış rapordan, baseline'da geçen testler üzerinden puanlayın. Sıfır test, zaman aşımı ya da başarısız test olmadan sıfırdan farklı bir çıkış kodu hüküm sayılmaz.
- Hayatta kalan mutant turu durdursun ve bir soru sordursun: bu duruma modülün dışından ulaşılabiliyor mu, listelenen testlerden biri değiştirilen satırı okuyabiliyor mu? Ephesus bilerek yüzde yazdırmıyor.
Depodaki diğer tek spec, bir mutantı doğrudan çıkış kodunun kendisine yöneltiyor. Bu spec'teki C1, README denetleyicisinden process.exit çağrısını kaldırıyor; betik hatasını yazdırıp 0 koduyla çıkıyor ve CI'dan geçiyor. O turdaki 8 gerçek mutantın hepsi öldü, ama C1 için önce yeni bir test yazılması gerekti: önceki testlerin hepsi check() fonksiyonunu doğrudan çağırıyordu.
GYM-008'in metrikleri 17 Ekim'de ölçülecekti ve 19 Ekim itibarıyla ledger bir sonuç kaydetmiyor.
29 Ekim güncellemesi: Rust sürümü mutantlarını kendisi üretiyor
Bu yazının yazıldığı gün, 19 Ekim'de, Ephesus ADR-0041 kararını kabul etti: sistemin her bileşeni Rust ile yeniden yazılıyor. mutate.cjs'i barındıran TypeScript ağacı davranışsal referans olarak donduruldu ve artık geliştirilmiyor. Yeni turlar spec'lerini .cargo/mutants.toml dosyasından okuyor.
Rust'taki tur, cargo xtask mutants, cargo-mutants'ı çalıştırıyor. Bu araç mutantları kendisi üretiyor; mutate.cjs ise elle hedeflenmiş mutantları spec dosyasından okuyor. Kontrol yeni bir biçimde korundu: tur, mutantların dörtte biri üzerinde hiçbir testle eşleşmeyen bir test filtresiyle yeniden koşuyor ve derlenebilen her mutantın hayatta kalması gerekiyor. Kontrol bunlardan birini yakalarsa tur INVALID sayılıyor.