From b0bdf9de73ebac0de99a5bf61127cb217c0ed8ba Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Feb 2020 19:30:05 +0100 Subject: [PATCH] new post --- posts/2020/2020-02-16-rust-ci.md | 75 +++++++++++++++++++++++++++++++ static/images/2020/ci-badges.png | Bin 0 -> 15966 bytes 2 files changed, 75 insertions(+) create mode 100755 posts/2020/2020-02-16-rust-ci.md create mode 100644 static/images/2020/ci-badges.png diff --git a/posts/2020/2020-02-16-rust-ci.md b/posts/2020/2020-02-16-rust-ci.md new file mode 100755 index 0000000..fcca2c4 --- /dev/null +++ b/posts/2020/2020-02-16-rust-ci.md @@ -0,0 +1,75 @@ + + + +Mon projet d'apprentissage de Rust avance doucement et c'est volontaire. Je prends le temps d'améliorer ma centaine de lignes de code, d'écrire des tests unitaires et je continue à lire sur Rust : la newsletter hebdomadaire et des liens sur [Reddit](https://www.reddit.com/r/rust/). Suite à la découverte de [Clippy](https://github.com/rust-lang/rust-clippy), l'utilitaire de détection d'erreurs de programmation, j'ai revu un code qui fonctionnait mais avait des failles potentielles et abusait du type *String* alors que des littéraux faisaient l'affaire. J'avais peu à peu mis des String partout pour ~~simplifier~~ contourner le borrowing (un classique du débutant en Rust semble-t-il) et par manque de vision sur la manière dont ma fonction serait utilisée. + +Finalement, j'ai un petit bout de mon projet (un seul module) avec ses tests unitaires, suffisamment complet pour mettre en place une intégration continue. Késako ? Il s'agit de s'assurer de la qualité du code au fur et à mesure de son écriture pour garantir une bonne qualité en fin de développement en incitant le développeur à améliorer son code tout au long du cycle. On peut aller plus ou moins loin dans les attentes d'une intégration continue : + +1. vérifier que le code compile, +2. vérifier que les tests unitaires s'exécutent sans erreur, +3. mesurer la couverture de code des tests, +4. vérifier la conformité par rapport à des règles : formatage, commentaires ou autre, +5. déployer le résultat pour le mettre à disposition mais on empiète sur la CD (Continuous Delivery). + +A une autre époque, j'aurais mis en place un Gitlab et une chaîne d'intégration de A à Z, j'aurais tâté du système, fait du scripting, déployé des containers, appris plein de trucs... Aujourd'hui je dois faire rapide et efficace donc j'ai tablé sur les fournisseurs de CI en ligne (Cloud CI providers pour les anglos) qui peuvent s'interfacer avec mon projet sur GitHub. J'ai testé Circle CI et Travis CI et c'est ce dernier qui m'a semblé [le mieux gérer les projets Rust](https://docs.travis-ci.com/user/languages/rust/). Techniquement, c'est très simple : on ajoute un fichier de configuration *.travis.yml* à son projet GitHub qui définit le pipeline d'intégration, on se connecte à Travis avec son identifiant GitHub et on configure son projet, et hop. A chaque commit Git, Travis déroule pipeline d'intégration. + +Ma configuration pour Rust a quelques particularités : +- on peut construire le projet avec la version stable, beta et nightly de Rust afin de prévoir les problèmes de compatiblité avec les prochaines versions. Je me suis limité à stable et beta +- Je ne m'arrête pas à la compilation et au déroulement des tests sans erreurs. La détection de *mauvais code* par Clippy est fatale et casse le build. Je suis là pour apprendre à écrire du bon code Rust, autant compliquer les choses. +- la couverture de code est réalisée à partir du binaire avec un outil non spécifique à Rust qui à partir des informations de déboguage d'un binaire calcule le taux de couverture du code par des tests. Cet outil s'appelle kcov. + +Mon fichier de configuration *.travis.yml* : + +```yaml +language: rust +sudo: required +rust: + - stable + - beta +before_script: + - rustup component add clippy +script: + - cargo clippy + - cargo test +jobs: + fast_finish: true +cache: cargo +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - cmake + - gcc + - binutils-dev + - libiberty-dev +after_success: | + wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && + tar xzf master.tar.gz && + cd kcov-master && + mkdir build && + cd build && + cmake .. && + make && + make install DESTDIR=../../kcov-build && + cd ../.. && + rm -rf kcov-master && + for file in target/debug/stacorust-*; do [ -x "${file}" ] || continue; mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && + bash <(curl -s https://codecov.io/bash) && + echo "Uploaded code coverage" +``` + +Les résultats de build avec moult détails son publiés sur le site de Travis CI : https://travis-ci.org/kianby/stacorust +et ceux de la couverture de code sur le site CodeCov : https://codecov.io/gh/kianby/stacorust + +![badges](/images/2020/ci-badges.png) + +> Cerise sur le gateau, on peut intégrer les badges sur la page README du projet pour avoir une vue synthétique de l'intégration continue. + + + + + + + diff --git a/static/images/2020/ci-badges.png b/static/images/2020/ci-badges.png new file mode 100644 index 0000000000000000000000000000000000000000..42d30a545bd38cbd2e8214a4362638d054441334 GIT binary patch literal 15966 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 Lx$l)C27&(