Jika pipeline Rust Anda lambat, flakey, atau hasilnya berbeda antara lokal dan CI, penyebabnya biasanya bukan satu hal: kombinasi test runner, desain matrix, dan strategi cache sering menjadi sumber utamanya. Untuk workflow tim, cargo-nextest sering lebih cocok daripada cargo test saat suite test mulai besar, terutama karena eksekusi test lebih efisien dan output kegagalan lebih mudah dibaca di CI.
Artikel ini membahas cara menyusun CI matrix stabil dengan cargo-nextest dan caching cerdas di GitHub Actions. Fokusnya praktis: kapan memakai nextest, bagaimana memisah job lint, test, dan build, cara memilih kombinasi OS/toolchain, kapan fail-fast perlu diaktifkan, dan bagaimana menghindari cache yang terlalu agresif sehingga hasil CI justru tidak konsisten.
Mengapa cargo-nextest relevan untuk CI Rust
cargo test tetap menjadi pilihan sederhana dan cukup baik untuk banyak proyek. Namun dalam CI tim, kebutuhan biasanya berkembang:
- jumlah test bertambah dan durasi eksekusi mulai terasa,
- test async atau integration test menjadi lebih banyak,
- kegagalan perlu dilihat dengan cepat tanpa membaca log yang terlalu panjang,
- parallelism perlu lebih terkontrol.
Di situ cargo-nextest berguna. Secara umum, nextest didesain sebagai runner test modern untuk ekosistem Rust, dengan fokus pada eksekusi yang efisien, isolasi yang lebih baik pada level proses test, dan pengalaman CI yang lebih nyaman.
Kapan tetap memakai cargo test
Pilih cargo test bila:
- proyek masih kecil,
- pipeline harus sesederhana mungkin,
- tim belum ingin menambah dependensi tooling CI,
- Anda belum memiliki masalah nyata pada durasi atau stabilitas test.
Kapan beralih ke cargo-nextest
Pilih cargo nextest run bila:
- test suite mulai besar dan CI sering menjadi bottleneck,
- Anda butuh output kegagalan yang lebih jelas,
- ada banyak integration test atau test async yang berjalan paralel,
- Anda ingin kontrol yang lebih baik atas perilaku test di CI.
Catatan praktis: nextest bukan pengganti semua perilaku
cargo testdalam setiap konteks. Untuk beberapa skenario lokal, developer tetap bisa memakaicargo test. Di CI, nextest sering dijadikan runner utama agar hasil lebih konsisten dan cepat.
Struktur pipeline: pisahkan lint, test, dan build
Salah satu kesalahan umum adalah menaruh semua langkah dalam satu job besar. Hasilnya:
- log sulit dibaca,
- sulit mengetahui bottleneck,
- cache dan artifact menjadi kurang terarah,
- job gagal terlalu dini padahal Anda masih ingin melihat hasil pemeriksaan lain.
Untuk proyek Rust tim, struktur yang lebih mudah dirawat biasanya seperti ini:
- lint: format dan clippy,
- test: unit test dan integration test dengan cargo-nextest,
- build: memastikan proyek benar-benar dapat dikompilasi pada target matrix yang dipilih.
Keuntungan pemisahan job
- Umpan balik lebih cepat: tim segera tahu apakah masalah ada di style, warning, test, atau kompilasi.
- Paralelisme lebih baik: lint tidak perlu menunggu test selesai.
- Diagnostik lebih jelas: waktu setiap job dapat dibandingkan dari run ke run.
- Perawatan lebih mudah: perubahan pada test tidak ikut merusak logika lint atau build.
Desain matrix yang stabil: jangan terlalu lebar
Matrix build sangat berguna, tetapi mudah disalahgunakan. Menjalankan semua kombinasi OS x toolchain x feature flag x target architecture sering membuat CI mahal, lambat, dan sulit dipahami.
Prinsip memilih matrix
Gunakan matrix untuk menjawab pertanyaan kompatibilitas yang memang penting bagi tim:
- OS utama: biasanya Linux sebagai jalur utama, lalu tambahkan macOS atau Windows bila memang didukung.
- Toolchain: umumnya
stablewajib;betaataunightlyhanya bila ada alasan kuat. - Jenis pemeriksaan: tidak semua OS perlu menjalankan semua langkah berat.
Strategi yang sering masuk akal:
- Lint hanya di satu OS, biasanya Linux.
- Test utama di Linux stable.
- Build verifikasi di Linux, macOS, dan Windows pada stable.
- Test tambahan di OS non-Linux hanya jika memang ada bug lintas platform atau dependensi sistem yang berbeda.
Contoh keputusan yang realistis
Jika sebagian besar deployment Anda berjalan di Linux server, jangan paksa seluruh suite integration test berjalan penuh di Windows dan macOS pada setiap commit. Cukup verifikasi build lintas platform dan jalankan test penuh di jalur yang paling representatif. Ini mengurangi kebisingan CI tanpa mengorbankan kepercayaan yang penting.
Contoh GitHub Actions: cargo-nextest, matrix, dan cache aman
Berikut contoh workflow yang menjaga struktur tetap rapi. Contoh ini memisahkan job lint, test, dan build, memakai nextest untuk test, serta menerapkan cache secara hati-hati.
name: ci
on:
push:
branches: [main]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo directories
uses: Swatinem/rust-cache@v2
with:
shared-key: lint-ubuntu-stable
cache-on-failure: true
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
toolchain: [stable]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@${{ matrix.toolchain }}
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Cache cargo directories
uses: Swatinem/rust-cache@v2
with:
shared-key: test-${{ matrix.os }}-${{ matrix.toolchain }}
cache-on-failure: true
- name: Run tests with nextest
run: cargo nextest run --workspace --all-features
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
toolchain: [stable]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@${{ matrix.toolchain }}
- name: Cache cargo directories
uses: Swatinem/rust-cache@v2
with:
shared-key: build-${{ matrix.os }}-${{ matrix.toolchain }}
cache-on-failure: true
- name: Build workspace
run: cargo build --workspace --all-features --locked
Mengapa struktur ini cukup sehat
- Lint terisolasi, jadi kegagalan format atau clippy tidak mencampur log dengan test.
- Test memakai nextest hanya di jalur utama yang paling representatif.
- Build matrix lintas OS tetap memberi jaminan kompatibilitas dasar.
- Fail-fast dimatikan pada matrix agar tim dapat melihat semua kegagalan lintas lingkungan dalam satu run.
Kenapa tidak semua job menjalankan all-features di semua OS?
Karena biaya CI bisa naik cepat. Bila memang perlu, Anda bisa mempertahankan --all-features untuk Linux test utama, lalu menyederhanakan build lintas OS sesuai kebutuhan proyek. Prinsipnya: validasi paling mahal dijalankan di tempat yang paling memberi sinyal, bukan di semua kombinasi sekaligus.
Strategi caching yang cerdas, bukan agresif
Cache bisa memangkas waktu CI, tetapi juga bisa menimbulkan hasil menyesatkan bila dipakai terlalu agresif. Dalam proyek Rust, area cache yang paling umum adalah:
- registry dan git dependencies Cargo,
- directory build seperti
target, - tooling tambahan yang diunduh saat workflow berjalan.
Apa yang aman untuk dicache
Secara umum, caching dependency Cargo hampir selalu bermanfaat. Caching target juga bisa membantu, tetapi risikonya lebih tinggi karena hasil kompilasi sangat sensitif terhadap:
- OS runner,
- toolchain Rust,
- fitur yang aktif,
- perubahan file
Cargo.lock, - variabel environment tertentu,
- build script dan native dependency.
Prinsip cache aman untuk tim
- Pisahkan cache berdasarkan OS dan toolchain.
- Jangan berbagi cache target secara membabi buta antara lint, test, dan build jika karakter bebannya berbeda jauh.
- Biarkan key cache ikut berubah saat dependency berubah, biasanya dengan basis file lock dan metadata workspace.
- Hindari cache yang terlalu “lengket” sehingga artefak lama terus dipakai walau konteks build sudah berubah.
Trade-off cache target directory
Caching target memang bisa mempercepat build incremental, tetapi ada beberapa trade-off:
- Kelebihan: kompilasi ulang lebih sedikit, terutama pada workspace besar.
- Kekurangan: cache bisa membesar, restore lebih lama, dan artefak lama berpotensi menyebabkan gejala aneh.
Pada beberapa proyek, waktu mengunduh dan mengekstrak cache besar justru mendekati waktu kompilasi ulang. Karena itu, ukur hasilnya. Jangan mengasumsikan semua cache selalu menghemat waktu.
Praktik aman: mulai dari cache dependency dan target yang dipisahkan per OS/toolchain. Jika pipeline tetap tidak stabil, pertimbangkan memperkecil cakupan cache target atau mematikannya sementara untuk memastikan sumber masalah.
Fail-fast yang tepat: kapan diaktifkan dan kapan dimatikan
Fail-fast menentukan apakah matrix lain dibatalkan saat satu kombinasi gagal. Tidak ada jawaban universal; pilihan ini tergantung tujuan job.
Saat fail-fast berguna
- job sangat mahal dan Anda ingin menghemat menit CI,
- satu kegagalan sudah cukup untuk menghentikan merge,
- matrix hanya dipakai untuk sampling, bukan diagnosis penuh.
Saat fail-fast sebaiknya dimatikan
- Anda ingin melihat semua kegagalan lintas OS dalam satu run,
- tim sedang menstabilkan kompatibilitas lintas platform,
- Anda ingin membedakan apakah masalah spesifik ke satu environment atau bersifat umum.
Untuk banyak tim, pendekatan yang masuk akal adalah:
- lint: tidak perlu matrix besar, jadi isu fail-fast hampir tidak relevan,
- test utama: biasanya cukup satu jalur representatif,
- build lintas OS: sering lebih berguna bila
fail-fast: falseagar semua hasil terlihat.
Membaca bottleneck pipeline dengan benar
Jika CI terasa lambat, jangan langsung menyalahkan test runner. Botol leher bisa ada di beberapa tempat:
- waktu checkout dan install toolchain,
- restore cache yang terlalu besar,
- kompilasi dependency berat,
- test integration yang menunggu I/O atau network lokal,
- native dependency pada OS tertentu.
Cara praktis mengidentifikasi bottleneck
- Lihat durasi tiap job: lint, test, build.
- Di dalam job, perhatikan langkah mana yang dominan: install, cache restore, compile, atau test run.
- Bandingkan run dengan cache hit dan cache miss.
- Bandingkan Linux vs macOS vs Windows; sering kali satu runner jauh lebih lambat.
- Perhatikan apakah test sebenarnya cepat, tetapi kompilasi sebelum test yang mahal.
Sinyal masalah yang sering salah dibaca
Jika cargo nextest run tampak lambat, bisa jadi masalahnya bukan nextest, melainkan kompilasi workspace sebelum test dijalankan. Dalam kasus seperti itu, optimasi yang lebih relevan adalah:
- mengecilkan matrix test,
- memperbaiki cache dependency/target,
- memisah integration test yang berat,
- mengurangi feature yang tidak perlu untuk jalur tertentu.
Jebakan umum pada CI Rust
1. Cache usang membuat hasil aneh
Gejalanya bisa berupa build gagal tanpa perubahan jelas, test hanya gagal di CI, atau perilaku yang hilang setelah cache dibersihkan. Penyebab umumnya adalah artefak lama yang tidak lagi cocok dengan konteks build terbaru.
Tips penanganan:
- ubah key cache saat struktur dependency berubah besar,
- pisahkan cache per OS/toolchain,
- bila perlu, nonaktifkan cache target sementara untuk verifikasi.
2. Test async tidak konsisten
Test async sering flakey bila:
- bergantung pada timing yang terlalu ketat,
- berbagi resource global,
- memakai port tetap, file sementara yang bentrok, atau state eksternal,
- mengandalkan urutan eksekusi yang tidak dijamin.
Karena nextest dan CI sama-sama mendorong paralelisme, bug seperti ini lebih cepat terlihat. Solusinya bukan mengurangi semua paralelisme secara membabi buta, tetapi memastikan test benar-benar terisolasi.
Periksa hal-hal berikut:
- gunakan resource unik per test bila memungkinkan,
- hindari sleep sebagai sinkronisasi utama,
- pastikan service mock atau database test tidak saling berbagi state tanpa reset.
3. Hasil lokal berbeda dengan CI
Masalah ini sangat umum. Penyebabnya biasanya:
- toolchain lokal berbeda,
- feature flag lokal tidak sama dengan CI,
- environment variable berbeda,
- sistem operasi atau shell berbeda,
- dependency native tersedia di lokal tetapi tidak di runner.
Langkah pencegahan yang sederhana namun efektif:
- gunakan
--lockedpada jalur build yang penting, - dokumentasikan command CI yang setara untuk dijalankan lokal,
- samakan jalur utama lokal dan CI, misalnya developer juga memakai
cargo nextest rununtuk reproduksi masalah tertentu.
4. Semua test dipaksa lintas OS tanpa kebutuhan nyata
Ini bukan hanya mahal, tetapi juga memperbesar area flake. Jika bug yang Anda cari sebagian besar ada di layer logika aplikasi yang tidak sensitif OS, jalankan test utama di satu OS representatif dan gunakan build lintas OS untuk verifikasi dasar.
Workflow tim yang mudah dirawat
Pipeline yang baik bukan sekadar cepat, tetapi juga mudah dipahami anggota tim baru. Beberapa prinsip yang membantu:
- Gunakan nama job yang jelas:
lint,test,buildlebih baik daripada satu job bernama umum. - Jangan terlalu banyak kondisi khusus di satu file workflow.
- Sederhanakan jalur utama lebih dulu, baru tambah matrix jika ada kebutuhan nyata.
- Pastikan command CI bisa dijalankan lokal untuk debugging yang lebih cepat.
Contoh command yang sebaiknya didokumentasikan untuk developer
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo nextest run --workspace --all-features
cargo build --workspace --all-features --locked
Dengan begitu, saat CI gagal, anggota tim tidak perlu menebak-nebak perintah apa yang dipakai pipeline.
Rekomendasi implementasi yang masuk akal
Jika Anda ingin mulai tanpa membuat workflow terlalu kompleks, urutan penerapan berikut biasanya aman:
- pisahkan job lint, test, dan build,
- jadikan Linux stable sebagai jalur test utama,
- pakai cargo-nextest untuk test CI,
- tambahkan build matrix untuk Linux, macOS, dan Windows bila proyek memang mendukungnya,
- aktifkan cache dependency dan target secara terpisah per OS/toolchain,
- ukur bottleneck sebelum menambah kombinasi matrix atau cache tambahan.
Pendekatan ini biasanya memberi keseimbangan yang baik antara kecepatan, stabilitas, dan kemudahan perawatan.
Penutup
CI matrix stabil dengan cargo-nextest dan caching cerdas bukan berarti membuat workflow semaksimal mungkin, tetapi memilih kombinasi yang memberi sinyal paling berguna dengan biaya operasional yang masuk akal. Untuk banyak tim Rust, hasil terbaik datang dari tiga hal: memisahkan job berdasarkan tujuan, menjalankan test utama dengan nextest di jalur representatif, dan menerapkan cache secara hati-hati agar cepat tanpa menyimpan masalah tersembunyi.
Jika pipeline Anda masih lambat atau flakey, mulai dari hal paling terukur: lihat durasi tiap langkah, kecilkan matrix yang tidak perlu, lalu evaluasi kembali cache target. Stabilitas CI biasanya meningkat bukan karena satu trik besar, melainkan karena sejumlah keputusan kecil yang tepat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!