diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6d291ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: ["master"] + pull_request: + branches: ["master"] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo build --verbose --all-features + + checks: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - name: Format + run: cargo fmt -- --check --verbose + - name: Lint + run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/coverage/README.md b/coverage/README.md index a75560e..883f722 100644 --- a/coverage/README.md +++ b/coverage/README.md @@ -2,8 +2,10 @@ This directory contains code coverage reports. -The coverage reports are generated using the [run.ps1](./run.ps1) script. To run the script, you need to install [cargo-llvm-cov](https://crates.io/crates/cargo-llvm-cov) and [cargo-nextest](https://crates.io/crates/cargo-nextest). +The coverage reports are generated using the [run.ps1](./run.ps1) (for Windows) and [run.sh](./run.sh) (for Linux/\*nix) scripts. + +To run the script, you need to install [cargo-llvm-cov](https://crates.io/crates/cargo-llvm-cov) and [cargo-nextest](https://crates.io/crates/cargo-nextest). > [!NOTE] > -> Because the crate requires GPU support, the coverage report is not run in CI. \ No newline at end of file +> Because the crate requires GPU support, the coverage report is not run in CI. diff --git a/coverage/coverage.rs b/coverage/coverage.rs new file mode 100644 index 0000000..398411d --- /dev/null +++ b/coverage/coverage.rs @@ -0,0 +1,147 @@ +macro_rules! cargo { + ($arg:literal $(, $fmt_args:expr),* $(,)?) => {{ + let cmd_arg = format!($arg, $($fmt_args),*); + let args = cmd_arg.split_whitespace(); + + println!("cargo {}", cmd_arg); + + let status = std::process::Command::new("cargo") + .args(args) + .status() + .expect("failed to execute process"); + + assert!(status.success(), "command 'cargo {}' failed", cmd_arg); + }}; +} + +fn main() { + let exe_path = std::env::current_exe().expect("current exe"); + + let mut manifest_path = exe_path.parent().expect("exe parent").to_path_buf(); + while std::fs::read_dir(&manifest_path) + .expect("read dir") + .find(|entry| entry.as_ref().expect("entry").file_name() == "Cargo.toml") + .is_none() + { + manifest_path = manifest_path.parent().expect("parent").to_path_buf(); + } + + let coverage_path = manifest_path.join("coverage"); + let examples_path = manifest_path.join("examples"); + let lcov_path = coverage_path.join("lcov.info"); + let lcov_path_str = lcov_path.to_str().expect("lcov path"); + let badge_path = coverage_path.join("badge.json"); + let model_path = examples_path.join("model.ply"); + let model_path_str = model_path.to_str().expect("model path"); + let output_path = coverage_path.join("output.ply"); + let output_path_str = output_path.to_str().expect("output path"); + + println!("Running coverage..."); + + cargo!("llvm-cov clean --workspace"); + + println!("Running 'modify' example"); + cargo!( + "llvm-cov run --example modify -- -m {model_path_str} -o {output_path_str} --rgb-or-hsv 0.1 0.9 0.8 --alpha 0.7 --contrast -1.0 --exposure 0.5 --gamma 1.2" + ); + + println!("Running 'modify' example with '--override-rgb' flag"); + cargo!( + "llvm-cov run --example modify -- -m {model_path_str} -o {output_path_str} --override-rgb --rgb-or-hsv 0.7 0.9 0.8 --alpha 0.7 --contrast -1.0 --exposure 0.5 --gamma 1.2" + ); + + println!("Running 'modify-selection' example"); + cargo!( + "llvm-cov run --example modify-selection -- -m {model_path_str} -o {output_path_str} -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --repeat 2 --offset 2.0 0.0 0.0 --contrast -1.0" + ); + + println!("Running 'modify-selection' example with '--shape Box' flag"); + cargo!( + "llvm-cov run --example modify-selection -- -m {model_path_str} -o {output_path_str} -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --shape Box --repeat 2 --offset 2.0 0.0 0.0 --contrast -1.0" + ); + + println!("Running 'filter-selection' example"); + cargo!( + "llvm-cov run --example filter-selection -- -m {model_path_str} -o {output_path_str} -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --repeat 2 --offset 2.0 0.0 0.0" + ); + + println!("Running 'filter-selection' example with '--shape Box' flag"); + cargo!( + "llvm-cov run --example filter-selection -- -m {model_path_str} -o {output_path_str} -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --shape Box --repeat 2 --offset 2.0 0.0 0.0" + ); + + println!("Running 'custom-modify-selection' example"); + cargo!( + "llvm-cov run --example custom-modify-selection -- -m {model_path_str} -o {output_path_str} -p 0.5 0.0 0.2 -r 2.0" + ); + + println!("Running doctests"); + // `--doctests` flag is currently unstable + // cargo!("llvm-cov --no-report --doctests"); + cargo!("test --doc"); + + println!("Running tests"); + cargo!("llvm-cov --no-report nextest --all-features"); + + println!("Generating coverage report"); + cargo!("llvm-cov report --lcov --output-path {lcov_path_str}"); + + println!("Generating badge"); + + let lcov = std::fs::read_to_string(&lcov_path).expect("read lcov.info"); + let mut total: u64 = 0; + let mut covered: u64 = 0; + + for line in lcov.lines() { + if !line.starts_with("DA:") { + continue; + } + + let mut parts = line[3..].split(','); + let _line_number = parts.next(); + let hits_str = parts.next(); + + let Some(hits_str) = hits_str else { + continue; + }; + let Ok(hits) = hits_str.parse::() else { + continue; + }; + + total += 1; + if hits != 0 { + covered += 1; + } + } + + let badge_percentage: u64 = if total == 0 { + 100 + } else { + ((covered as f32 / total as f32) * 100.0).round() as u64 + }; + + let badge_color = if badge_percentage >= 80 { + "brightgreen" + } else if badge_percentage >= 50 { + "yellow" + } else { + "red" + }; + + let badge_json = format!( + r#" +{{ + "schemaVersion": 1, + "label": "coverage", + "message": "{badge_percentage}%", + "color": "{badge_color}" +}} + "# + ); + std::fs::write(&badge_path, badge_json.trim().to_owned() + "\n").expect("write badge.json"); + + println!("Cleaning up"); + std::fs::remove_file(&output_path).expect("remove output.ply"); + + println!("Done"); +} diff --git a/coverage/run.ps1 b/coverage/run.ps1 index 69e830a..9c3d3c4 100644 --- a/coverage/run.ps1 +++ b/coverage/run.ps1 @@ -1,72 +1,9 @@ -$BASE_DIR = $PSScriptRoot -$EXAMPLES_PATH = "$BASE_DIR/../examples" -$LCOV_PATH = "$BASE_DIR/lcov.info" +$TARGET_DIR = "$PSScriptRoot\..\target\coverage" -echo "Running coverage..." - -cargo llvm-cov clean --workspace - -echo "Running 'modify' example" -cargo llvm-cov run --example modify -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" --rgb-or-hsv 0.1 0.9 0.8 --alpha 0.7 --contrast "-1.0" --exposure 0.5 --gamma 1.2 - -echo "Running 'modify' example with '--override-rgb' flag" -cargo llvm-cov run --example modify -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" --override-rgb --rgb-or-hsv 0.7 0.9 0.8 --alpha 0.7 --contrast "-1.0" --exposure 0.5 --gamma 1.2 - -echo "Running 'modify-selection' example" -cargo llvm-cov run --example modify-selection -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --repeat 2 --offset 2.0 0.0 0.0 --contrast "-1.0" - -echo "Running 'modify-selection' example with '--shape Box' flag" -cargo llvm-cov run --example modify-selection -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --shape Box --repeat 2 --offset 2.0 0.0 0.0 --contrast "-1.0" - -echo "Running 'filter-selection' example" -cargo llvm-cov run --example filter-selection -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --repeat 2 --offset 2.0 0.0 0.0 - -echo "Running 'filter-selection' example with '--shape Box' flag" -cargo llvm-cov run --example filter-selection -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" -p 0.5 1.0 0.5 -r 0.2 0.0 0.0 1.0 -s 1 1 1 --shape Box --repeat 2 --offset 2.0 0.0 0.0 - -echo "Running 'custom-modify-selection' example" -cargo llvm-cov run --example custom-modify-selection -- -m "$EXAMPLES_PATH/model.ply" -o "$BASE_DIR/output.ply" -p 0.5 0.0 0.2 -r 2.0 - -# `--doctests` flag is currently unstable -# echo "Running doctests" -# cargo llvm-cov --no-report --doctests -cargo test --doc # Run doctests without tracking coverage - -echo "Running tests" -cargo llvm-cov --no-report nextest - -echo "Generating coverage report" -cargo llvm-cov report --lcov --output-path "$LCOV_PATH" - -echo "Generating badge" -$total = 0 -$covered = 0 -Select-String -Path "$LCOV_PATH" -Pattern "DA:" | ForEach-Object { - if ($_ -match "DA:\d+,(\d+)") { - $total++ - if ($matches[1] -ne "0") { - $covered++ - } - } +if (!(Test-Path -Path $TARGET_DIR)) { + New-Item -ItemType Directory -Path $TARGET_DIR | Out-Null } -$badge_percentage = if ($total -eq 0) { 100 } else { [math]::Round(($covered / $total) * 100) } -$badge_color = if ($badge_percentage -ge 80) { - "brightgreen" -} elseif ($badge_percentage -ge 50) { - "yellow" -} else { - "red" -} - -"{ - `"schemaVersion`": 1, - `"label`": `"coverage`", - `"message`": `"$badge_percentage%`", - `"color`": `"$badge_color`" -}" | Out-File -FilePath "$BASE_DIR/badge.json" -Encoding ascii - -echo "Cleaning up" -rm "$BASE_DIR/output.ply" - -echo "Done" \ No newline at end of file +rustc "$PSScriptRoot\coverage.rs" -o "$TARGET_DIR\coverage.exe" +& "$TARGET_DIR\coverage.exe" +Remove-Item "$TARGET_DIR\coverage.exe" \ No newline at end of file diff --git a/coverage/run.sh b/coverage/run.sh new file mode 100644 index 0000000..4806ef0 --- /dev/null +++ b/coverage/run.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +set -eu + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +TARGET_DIR="$SCRIPT_DIR/../target/coverage" + +mkdir -p "$TARGET_DIR" + +OUT_BIN="$TARGET_DIR/coverage" + +cleanup() { + rm -f "$OUT_BIN" +} +trap cleanup 0 + +rustc "$SCRIPT_DIR/coverage.rs" -o "$OUT_BIN" +"$OUT_BIN"