CI untuk Rust

fmt, clippy, dan gerbang pengujian pada pipeline.

Daftar isi

Topik ini membahas pemasangan gerbang kualitas pada pipeline continuous integration (CI) untuk proyek Rust, yaitu cargo fmt --check untuk keseragaman format, cargo clippy untuk pemeriksaan lanjutan, dan cargo test untuk seluruh rangkaian pengujian. Pembahasan mencakup struktur berkas pipeline GitHub Actions beserta arti setiap langkahnya, cara menjalankan ketiga gerbang secara lokal sebelum push dilakukan, serta cara membaca laporan kegagalan setiap gerbang. Contoh memakai proyek penyaring-ci yang lolos ketiga gerbang dan dijalankan sungguhan. Setelah membacanya, berkas pipeline dapat dipasang pada repository dan kegagalan gerbang dapat diperbaiki sebelum mencapai server CI.

Tiga gerbang kualitas#

Pipeline CI menjalankan rangkaian perintah setiap kali perubahan di-push ke repository, dan setiap perintah yang keluar dengan kode bukan nol menghentikan rangkaian itu serta menandai pipeline gagal. Tiga gerbang berikut menjadi susunan yang umum untuk proyek Rust karena masing-masing memeriksa lapis yang berbeda, yaitu gaya penulisan, idiom bahasa, dan perilaku program.

GerbangPerintahYang diperiksaBentuk laporan kegagalan
formatcargo fmt --all --checkkeseragaman gaya penulisan kodediff perbaikan format
pemeriksaancargo clippy --all-targets -- -D warningslinter umum dan idiom yang lebih baikpesan error dengan saran perbaikan
pengujiancargo test --allunit test, integration test, dan doc testlaporan kegagalan pengujian

Gerbang format memakai rustfmt yang sama dengan yang dipakai saat menyunting kode, dan tanda --check mengubah perilakunya dari menulis berkas menjadi mencetak diff lalu keluar dengan kode satu. Gerbang pemeriksaan memakai clippy yang menerapkan linter rustc beserta linter idiomnya, dan tanda -D warnings setelah -- menaikkan seluruh peringatan menjadi error sehingga pipeline gagal. Gerbang pengujian menjalankan ketiga jenis pengujian yang dibahas pada modul ini dalam satu perintah.

Urutan gerbang menentukan kecepatan umpan balik. Format diperiksa tanpa kompilasi sehingga selesai dalam hitungan detik, clippy mengompilasi seluruh target dalam mode pemeriksaan, dan pengujian melakukan kompilasi penuh lalu menjalankan program. Kegagalan format yang terdeteksi lebih dahulu menghemat waktu kompilasi, sebab perubahan yang ditolak karena alasan gaya tidak dilanjutkan ke tahap kompilasi.

Berkas pipeline GitHub Actions#

Layanan GitHub Actions membaca berkas YAML pada direktori .github/workflows/ dan menjalankan langkah-langkahnya pada mesin yang disediakan. Berkas berikut memasang ketiga gerbang untuk setiap push ke branch main dan setiap pull request.

yaml ci-rust.yml
name: ci-rust

on:
  push:
    branches: [main]
  pull_request:

jobs:
  gerbang:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      - name: Format
        run: cargo fmt --all --check
      - name: Clippy
        run: cargo clippy --all-targets -- -D warnings
      - name: Test
        run: cargo test --all

Bagian on menentukan peristiwa pemantik, yaitu push ke branch main dan setiap pull request. Bagian jobs memuat satu pekerjaan bernama gerbang yang berjalan pada mesin Ubuntu, dan steps mengurutkan langkah-langkahnya. Langkah uses: actions/checkout@v4 menyalin isi repository ke mesin tersebut, sedangkan uses: dtolnay/rust-toolchain@stable memasang toolchain stabil beserta komponen rustfmt dan clippy yang tidak terpasang secara bawaan. Setiap langkah run menjalankan satu perintah, dan kegagalan salah satunya menghentikan pekerjaan sehingga langkah berikutnya tidak dijalankan. Rangkaian lengkap dari push sampai pipeline dinyatakan lolos dirangkum pada diagram berikut.

push ke repositorymemicu pipelinecargo fmt --checkcargo clippy -D warningscargo testpipeline lolosperubahan siap digabung
Rangkaian gerbang pada pipeline CI

Berkas YAML hanya dijalankan oleh layanan CI, sehingga isinya tidak dapat diuji secara lokal. Nilai praktisnya terletak pada isi setiap langkah run, karena ketiga baris itu adalah perintah lokal biasa yang dapat diperiksa lebih dahulu pada mesin sendiri. Pemeriksaan lokal semacam itu dilakukan pada proyek contoh penyaring-ci.

Proyek contoh yang lolos ketiga gerbang#

Proyek penyaring-ci memuat pustaka penyaring baris catatan dengan dua fungsi publik dan dua unit test. Seluruh berkasnya sengaja lolos ketiga gerbang sehingga keluaran pemeriksaan bersih dan dapat dibandingkan dengan laporan kegagalan yang disusun setelahnya.

toml Cargo.toml
[package]
name = "penyaring-ci"
version = "0.1.0"
edition = "2021"
rust src/lib.rs
// tidak-dijalankan sendirian oleh rustc karena berkas pustaka tanpa fungsi main.

//! Penyaring baris catatan berdasarkan kata kunci.

/// Mengembalikan seluruh baris yang memuat kata kunci.
pub fn cari_baris<'a>(catatan: &'a str, kata_kunci: &str) -> Vec<&'a str> {
    catatan
        .lines()
        .filter(|baris| baris.contains(kata_kunci))
        .collect()
}

/// Menghitung banyaknya baris yang memuat kata kunci.
pub fn hitung_baris(catatan: &str, kata_kunci: &str) -> usize {
    cari_baris(catatan, kata_kunci).len()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cari_dua_baris() {
        let catatan = "beli buku\nbeli pulpen\njual buku";
        assert_eq!(cari_baris(catatan, "beli").len(), 2);
    }

    #[test]
    fn hitung_nol_bila_tidak_ada() {
        assert_eq!(hitung_baris("halo", "selamat"), 0);
    }
}

Pemeriksaan dilakukan pada proyek di luar pohon konten situs. Berkas ci-rust.yml dari bundle disalin ke posisi kanonisnya pada .github/workflows/ci.yml agar susunan proyek sama dengan repository yang sesungguhnya.

bash
# siapkan proyek percobaan yang bersih di luar pohon konten
rm -rf /tmp/proyek-ci
cargo new /tmp/proyek-ci --lib -q --vcs none
# salin berkas proyek dari bundle ke posisinya masing-masing
cp Cargo.toml /tmp/proyek-ci/Cargo.toml
cp src/lib.rs /tmp/proyek-ci/src/lib.rs
# pasang berkas pipeline pada posisi kanonisnya
mkdir -p /tmp/proyek-ci/.github/workflows
cp ci-rust.yml /tmp/proyek-ci/.github/workflows/ci.yml

Menjalankan ketiga gerbang secara lokal#

Ketiga perintah berikut sama dengan tiga langkah run pada berkas pipeline, dengan tambahan --manifest-path karena perintah dijalankan dari luar direktori proyek. Gerbang yang lolos tidak mencetak apa pun dan keluar dengan kode nol, sedangkan gerbang pengujian tetap mencetak laporannya.

bash
# periksa format tanpa menulis berkas
cargo fmt --manifest-path /tmp/proyek-ci/Cargo.toml --all --check
bash
# periksa seluruh target dengan clippy dan peringatan dinaikkan menjadi error
cargo clippy --manifest-path /tmp/proyek-ci/Cargo.toml --all-targets -q -- -D warnings 2>/dev/null
bash
# jalankan seluruh rangkaian pengujian secara berurutan
cargo test --manifest-path /tmp/proyek-ci/Cargo.toml -q -- --test-threads=1 2>/dev/null | sed 's/; finished in [0-9.]*s$//'
text Keluaran
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Dua blok pertama tidak diikuti blok keluaran karena keduanya tidak mencetak apa pun ketika lolos. Kode keluar setiap perintah dapat diperiksa dengan echo $? yang mencetak 0 saat lolos. Laporan pengujian memuat dua bagian, yaitu dua unit test pada pustaka dan bagian doc test yang kosong karena dokumentasi fungsi belum memuat contoh berpagar.

Membaca kegagalan gerbang#

Bentuk kegagalan kedua gerbang pertama dapat diperiksa dengan sengaja merusak berkas pustaka lalu memulihkannya. Blok pertama menghapus spasi sebelum usize sehingga rustfmt melaporkan diff, dan blok kedua menambahkan kata kunci return yang oleh clippy dianggap tidak perlu pada baris terakhir fungsi.

bash
# rusak format pada tanda panah fungsi hitung_baris
sed -i 's/-> usize {/->usize {/' /tmp/proyek-ci/src/lib.rs
# periksa format lalu perhatikan diff yang dicetak
cargo fmt --manifest-path /tmp/proyek-ci/Cargo.toml --all --check
# pulihkan berkas asli dari bundle
cp src/lib.rs /tmp/proyek-ci/src/lib.rs
text Keluaran
Diff in /tmp/proyek-ci/src/lib.rs:11:
 }
 
 /// Menghitung banyaknya baris yang memuat kata kunci.
-pub fn hitung_baris(catatan: &str, kata_kunci: &str) ->usize {
+pub fn hitung_baris(catatan: &str, kata_kunci: &str) -> usize {
     cari_baris(catatan, kata_kunci).len()
 }
 

Baris Diff in menyebut berkas dan baris pertama yang berbeda, baris berawalan - menunjukkan tulisan saat ini, dan baris berawalan + menunjukkan hasil rapi yang diinginkan rustfmt. Perbaikannya cukup menjalankan cargo fmt tanpa --check pada direktori proyek, karena versi tanpa --check menulis perbaikan itu langsung ke berkas.

bash
# tambahkan return yang tidak perlu pada baris terakhir fungsi
sed -i 's|cari_baris(catatan, kata_kunci).len()|return cari_baris(catatan, kata_kunci).len();|' /tmp/proyek-ci/src/lib.rs
# periksa seluruh target dengan clippy dan peringatan dinaikkan menjadi error
cargo clippy --manifest-path /tmp/proyek-ci/Cargo.toml --all-targets -q -- -D warnings
# pulihkan berkas asli dari bundle
cp src/lib.rs /tmp/proyek-ci/src/lib.rs
text Keluaran
error: unneeded `return` statement
  --> src/lib.rs:15:5
   |
15 |     return cari_baris(catatan, kata_kunci).len();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#needless_return
   = note: `-D clippy::needless-return` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(clippy::needless_return)]`
help: remove `return`
   |
15 -     return cari_baris(catatan, kata_kunci).len();
15 +     cari_baris(catatan, kata_kunci).len()
   |

error: could not compile `penyaring-ci` (lib) due to 1 previous error
error: could not compile `penyaring-ci` (lib test) due to 1 previous error

Laporan clippy memuat nama linter needless_return, lokasi persisnya pada kolom -->, dan saran perbaikan yang menggambarkan bentuk kode yang diharapkan. Baris catatan -D clippy::needless-return implied by -D warnings menjelaskan mengapa peringatan berubah menjadi error, dan dua baris terakhir menunjukkan bahwa pustaka beserta target pengujianannya sama-sama gagal dikompilasi. Seluruh gerbang pengujian tidak dijalankan karena langkah clippy pada pipeline berhenti lebih dahulu, sehingga urutan langkah menentukan laporan mana yang terlihat lebih dulu.

Kesalahan umum#

Menjalankan cargo fmt tanpa --check pada pipeline. Tanpa tanda itu, rustfmt menulis berkas dan langkah selalu lolos, sehingga format tidak pernah diperiksa. Simpan penulisan berkas untuk mesin lokal, dan biarkan pipeline hanya memeriksa.

Melupakan komponen rustfmt dan clippy. Toolchain stabil yang dipasang layanan CI tidak selalu memuat kedua biner tersebut, sehingga langkah gagal dengan perintah tidak ditemukan alih-alih laporan yang berarti. Cantumkan komponennya pada langkah pemasangan toolchain sebagaimana berkas pipeline di atas.

Menghilangkan -D warnings pada clippy. Tanpa tanda itu, peringatan tidak menggagalkan pipeline dan menumpuk dari waktu ke waktu sampai satu linter tertentu dinaikkan menjadi error pada versi clippy yang lebih baru. Naikkan seluruh peringatan sejak awal agar setiap temuan diperbaiki pada perubahan yang memuatnya.

Ringkasan#

  • Pipeline CI menjalankan cargo fmt --all --check, cargo clippy --all-targets -- -D warnings, dan cargo test --all secara berurutan, dan kegagalan satu langkah menghentikan langkah berikutnya.
  • Setiap baris run pada berkas .github/workflows/ adalah perintah lokal biasa, sehingga ketiga gerbang dapat dijalankan lebih dahulu dengan --manifest-path sebelum push dilakukan.
  • Gerbang format yang gagal mencetak diff dengan baris - dan +, sedangkan gerbang clippy memuat nama linter, lokasi, dan saran perbaikannya.
  • Tanda --check menjaga fmt tidak menulis berkas, dan -D warnings menaikkan peringatan clippy menjadi error agar pipeline benar-benar gagal.

Referensi#