Flaky test memperlambat siklus iterasi dan merusak kepercayaan pada pipeline pengujian. Pendekatan konvensional yang mengandalkan trigger ulang di CI membuang waktu tunggu komputasi dan context switching. Solusi paling efisien adalah mereproduksi dan mengisolasi kegagalan langsung dari lingkungan pengeditan secara deterministik.
Artikel ini mendemonstrasikan implementasi workflow verifikasi di GNU Emacs menggunakan paket Embark. Dengan mendefinisikan custom target finder dan context-aware actions, Anda dapat mengekstrak identifier test dari buffer kode maupun compilation-mode, lalu mengeksekusinya secara asinkron tanpa memblokir antarmuka editor.
Arsitektur Target Finder Embark
Embark bekerja dengan memeriksa teks di sekitar point menggunakan daftar fungsi yang terdaftar di embark-target-finders. Fungsi finder mengembalikan cons cell berupa (target-type . target-value). Ketika tipe target dikenali, Embark memetakan target tersebut ke keymap spesifik yang terdaftar di embark-keymap-alist.
Untuk workflow pengujian, target finder harus mengekstrak nama fungsi pengujian terdekat jika kursor berada di dalam unit test (misalnya fungsi test_* pada Python atau fungsi beranotasi #[test] pada Rust).
(defun my/embark-target-test-at-point ()
"Deteksi nama fungsi test di sekitar point untuk Python dan Rust."
(when (derived-mode-p 'python-mode 'python-ts-mode 'rust-mode 'rust-ts-mode)
(save-excursion
(let ((case-fold-search nil))
(when (re-search-backward
"\\(?:def\\s-+\\([a-zA-Z0-9_]+\\)\\|fn\\s-+\\([a-zA-Z0-9_]+\\)\\)"
(line-beginning-position -10) t)
(let ((fn-name (or (match-string-no-properties 1)
(match-string-no-properties 2))))
(when (string-match-p "^test_" fn-name)
(cons 'test-target fn-name))))))))
(add-to-list 'embark-target-finders #'my/embark-target-test-at-point)Target Finder untuk Stack Trace pada Compilation Buffer
Selain buffer kode sumber, kegagalan uji sering kali diperiksa melalui output buffer compilation-mode. Finder berikut mengekstrak nama test yang gagal langsung dari output ringkasan stack trace pytest atau cargo test:
(defun my/embark-target-test-from-compilation ()
"Ekstrak nama test dari baris error di compilation-mode."
(when (derived-mode-p 'compilation-mode)
(save-excursion
(beginning-of-line)
(cond
;; Pola pytest: FAILED tests/test_api.py::test_user_session
((re-search-forward "::\\(test_[a-zA-Z0-9_]+\\)" (line-end-position) t)
(cons 'test-target (match-string-no-properties 1)))
;; Pola cargo test: test tests::test_worker ... FAILED
((re-search-forward "test \\(?:[a-zA-Z0-9_]+::\\)+\\(test_[a-zA-Z0-9_]+\\)" (line-end-position) t)
(cons 'test-target (match-string-no-properties 1)))))))
(add-to-list 'embark-target-finders #'my/embark-target-test-from-compilation)Implementasi Custom Actions: Single Run dan Stress Loop
Setelah target dikenali sebagai test-target, kaitkan dengan keymap khusus untuk menjalankan serangkaian aksi: eksekusi terisolasi, stress testing perulangan untuk memicu race condition, atau navigasi langsung ke definisinya.
(defun my/test-run-single (target)
"Eksekusi single test terisolasi secara asinkron."
(interactive "sTarget test: ")
(let ((cmd (if (derived-mode-p 'rust-mode 'rust-ts-mode)
(format "cargo test %s -- --exact" target)
(format "pytest -k %s -vv" target))))
(compile cmd)))
(defun my/test-stress-loop (target)
"Uji ketahanan target test berulang kali untuk mengisolasi flakiness."
(interactive "sTarget test: ")
(let ((cmd (if (derived-mode-p 'rust-mode 'rust-ts-mode)
(format "for i in $(seq 1 50); do cargo test %s -- --exact || break; done" target)
;; pytest-repeat plugin jika ada, fallback ke loop shell
(format "for i in $(seq 1 50); do pytest -k %s -x || break; done" target))))
(compile cmd)))
(defvar-keymap my-embark-test-map
:doc "Keymap Embark untuk test-target."
"r" #'my/test-run-single
"s" #'my/test-stress-loop)
(add-to-list 'embark-keymap-alist '(test-target . my-embark-test-map))Menjaga Eksekusi Asinkronus Tanpa Freezing
Pengujian berulang (50 hingga 100 iterasi) membutuhkan waktu lama. Menjalankan subproses via shell-command-to-string atau fungsi sinkronus lain akan memblokir thread utama Emacs (UI freezing).
Menggunakan fungsi bawaan compile atau compilation-start mengalihkan eksekusi ke subproses asinkronus murni dengan properti berikut:
- Non-blocking I/O: Input/output dialirkan ke buffer
*compilation*secara inkremental tanpa mengganggu buffer editing aktif. - Jump to Error: Buffer hasil mewarisi parsing otomatis regex file/line sehingga tombol
next-error(M-g n) tetap berfungsi untuk menuju titik assertion failure. - Process Termination: Proses loop dapat dihentikan kapan saja dengan mengeksekusi
kill-compilation(C-c C-k).
Trade-off: Fast Local Loop vs Eksekusi CI
Workflow lokal via Embark menawarkan isolasi instan dengan overhead nyaris nol. Namun, ada batas batas struktural antara loop lokal dan pipeline CI:
- Kecepatan vs Kebersihan State: Eksekusi lokal mempertahankan disk state, cache database, atau temporary files yang dapat menyebabkan false positives atau false negatives. Sebaliknya, CI menjalankan lingkungan bersih (clean container) namun lambat.
- Paralelisme dan Concurrency: Stress test lokal sering kali dijalankan pada single process loop. Flaky test yang dipicu oleh race condition antar-worker (multi-thread atau multi-process) memerlukan flag paralelisme tambahan seperti
pytest -n auto. - Resource Contention: Loop 50 iterasi dapat menguras CPU lokal. Batasi eksekusi stress test hanya pada unit terkecil yang ditargetkan, bukan seluruh test suite.
Integrasi aksi Embark ini memangkas latency investigasi flaky test dari hitungan menit via CI turnaround menjadi hitungan detik di dalam buffer editor.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!