Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
fmt:
name: Format check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install rustfmt
run: rustup component add rustfmt
- name: Check formatting
run: cargo fmt --all -- --check

clippy:
name: Lint (clippy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install clippy
run: rustup component add clippy
- name: Run clippy
# Mirrors `make lint`: every clippy and compiler warning is an error.
run: cargo clippy --all-targets --all-features -- -D warnings

test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Run tests
run: cargo test --all-targets
- name: Run doctests
run: cargo test --doc

build:
name: Release build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Build release binary
run: cargo build --release

coverage:
name: Coverage (90% floor)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install llvm-tools
run: rustup component add llvm-tools
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Check coverage meets the floor
# Mirrors `make coverage-check`.
run: cargo llvm-cov --fail-under-lines 90 --all-targets --all-features
12 changes: 7 additions & 5 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,28 @@ make lint
Run all CI checks locally:

```bash
make ci
make
```

This will:
1. Check code formatting
2. Run clippy linter
3. Run all tests
2. Run the clippy linter
3. Run all tests and check the 90% line-coverage floor
4. Run doctests
5. Build the release binary

### Code Formatting

We use `rustfmt` with custom configuration (see `rustfmt.toml`). Format your code before committing:

```bash
make fmt
make fmt-fix
```

To check if code is properly formatted without modifying files:

```bash
make fmt-check
make fmt
```

### Linting
Expand Down
98 changes: 55 additions & 43 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
.PHONY: help build test check fmt lint clean coverage install-tools nextest mutants setup coverage-serve unused-deps audit
.PHONY: all help build build-release test test-doc nextest mutants check fmt fmt-fix clippy lint lint-fix coverage coverage-ci coverage-check coverage-serve clean setup install-tools watch bench doc outdated unused-deps audit

# Ensure cargo and tools are on PATH
export PATH := $(HOME)/.cargo/bin:$(PATH)

# Default target
# Default target: every check the CI workflow runs. Green here = green in CI.
# (coverage-check runs the full test suite internally with instrumentation)
all: fmt clippy coverage-check test-doc build-release

help:
@echo "Available targets:"
@echo " make build - Build the project"
@echo " make setup - Install tools and configure git hooks (run once after clone)"
@echo " make test - Run all tests"
@echo " make nextest - Run tests with cargo-nextest"
@echo " make mutants - Run mutation testing with cargo-mutants"
@echo " make check - Run cargo check"
@echo " make fmt - Format code with rustfmt"
@echo " make fmt-check - Check code formatting"
@echo " make lint - Run clippy linter"
@echo " make lint-fix - Run clippy with auto-fix"
@echo " make coverage - Generate code coverage report (HTML)"
@echo " make coverage-serve - Generate and serve coverage report on localhost:8080"
@echo " make coverage-ci - Generate coverage report for CI (LCOV)"
@echo " make unused-deps - Check for unused dependencies"
@echo " make audit - Run dependency security audit"
@echo " make clean - Clean build artifacts"
@echo " make install-tools - Install required development tools"
@echo " make ci - Run all CI checks (fmt, lint, test)"
@echo " all Run every CI check: fmt, clippy, coverage, doctests, release build"
@echo " build Build the project (debug)"
@echo " build-release Build the release binary"
@echo " test Run all tests"
@echo " test-doc Run doctests"
@echo " nextest Run tests with cargo-nextest"
@echo " mutants Run mutation testing with cargo-mutants"
@echo " check Run cargo check"
@echo " fmt Check code formatting"
@echo " fmt-fix Format the code"
@echo " clippy Run the Clippy linter"
@echo " lint Run formatting check and Clippy"
@echo " lint-fix Run Clippy with auto-fix"
@echo " coverage Generate code coverage report (HTML)"
@echo " coverage-check Check coverage meets the 90% line floor"
@echo " coverage-ci Generate coverage report for CI (LCOV)"
@echo " coverage-serve Generate and serve coverage report on localhost:8080"
@echo " doc Build API documentation"
@echo " audit Run dependency security audit"
@echo " unused-deps Check for unused dependencies"
@echo " outdated Check for outdated dependencies"
@echo " clean Remove build artifacts"
@echo " setup Install tools and configure git hooks (run once after clone)"
@echo " install-tools Install required development tools"

# Build the project
build:
Expand All @@ -37,6 +46,10 @@ build-release:
test:
cargo test --all-targets --all-features

# Run doctests (not covered by --all-targets)
test-doc:
cargo test --doc --all-features

# Run tests with cargo-nextest (faster, process-per-test)
nextest:
@command -v cargo-nextest >/dev/null 2>&1 || { echo "cargo-nextest not installed. Run 'make install-tools' first."; exit 1; }
Expand All @@ -51,18 +64,21 @@ mutants:
check:
cargo check --all-targets --all-features

# Format code
fmt:
cargo fmt --all

# Check formatting without modifying files
fmt-check:
fmt:
cargo fmt --all -- --check

# Run clippy linter
lint:
# Format code
fmt-fix:
cargo fmt --all

# Run the Clippy linter; every warning is an error
clippy:
cargo clippy --all-targets --all-features -- -D warnings

# Fast pre-commit loop: formatting check plus Clippy, no tests
lint: fmt clippy

# Run clippy with auto-fix
lint-fix:
cargo clippy --all-targets --all-features --fix --allow-dirty --allow-staged
Expand All @@ -78,6 +94,16 @@ coverage-ci:
@command -v cargo-llvm-cov >/dev/null 2>&1 || { echo "cargo-llvm-cov not installed. Run 'make install-tools' first."; exit 1; }
cargo llvm-cov --lcov --output-path coverage/lcov.info --all-targets --all-features

# Check coverage meets 90% threshold (runs tests internally)
coverage-check:
@command -v cargo-llvm-cov >/dev/null 2>&1 || { echo "cargo-llvm-cov not installed. Run 'make install-tools' first."; exit 1; }
cargo llvm-cov --fail-under-lines 90 --all-targets --all-features

# Generate and serve coverage report
coverage-serve: coverage
@echo "Serving coverage report at http://localhost:8080"
python3 -m http.server 8080 -d coverage/html

# Clean build artifacts
clean:
cargo clean
Expand All @@ -103,15 +129,6 @@ install-tools:
cargo install cargo-audit
@echo "All tools installed successfully!"

# Run all CI checks (coverage-check runs tests internally with instrumentation)
ci: fmt-check lint coverage-check
@echo "All CI checks passed!"

# Check coverage meets 95% threshold (runs tests internally)
coverage-check:
@command -v cargo-llvm-cov >/dev/null 2>&1 || { echo "cargo-llvm-cov not installed. Run 'make install-tools' first."; exit 1; }
cargo llvm-cov --fail-under-lines 95 --all-targets --all-features

# Watch for changes and run tests
watch:
@command -v cargo-watch >/dev/null 2>&1 || { echo "cargo-watch not installed. Install with: cargo install cargo-watch"; exit 1; }
Expand All @@ -121,20 +138,15 @@ watch:
bench:
cargo bench

# Generate and open documentation
# Build API documentation
doc:
cargo doc --open --all-features
cargo doc --no-deps --all-features

# Check for outdated dependencies
outdated:
@command -v cargo-outdated >/dev/null 2>&1 || { echo "cargo-outdated not installed. Install with: cargo install cargo-outdated"; exit 1; }
cargo outdated

# Generate and serve coverage report
coverage-serve: coverage
@echo "Serving coverage report at http://localhost:8080"
python3 -m http.server 8080 -d coverage/html

# Check for unused dependencies
unused-deps:
@command -v cargo-machete >/dev/null 2>&1 || { echo "cargo-machete not installed. Run 'make install-tools' first."; exit 1; }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Common tasks are exposed through `make`:
make help # List all available targets
make build # Build the project
make test # Run the test suite
make ci # Run all CI checks (format, lint, test)
make # Run all CI checks (format, lint, coverage, doctests, release build)
```

See [DEVELOPMENT.md](DEVELOPMENT.md) for the full development workflow, tooling,
Expand Down
8 changes: 0 additions & 8 deletions crates/codedeploy-commands/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ impl Client {
host_identifier: &str,
) -> Result<Option<HostCommandInstance>, Error> {
let input = PollHostCommandInput { host_identifier: host_identifier.to_string() };
// GRCOV_STOP_COVERAGE
let output: PollHostCommandOutput = self.call("PollHostCommand", &input)?;
Ok(output.host_command)
// GRCOV_BEGIN_COVERAGE
}

/// Acknowledge receipt of a host command.
Expand All @@ -80,11 +78,9 @@ impl Client {
host_command_identifier: host_command_identifier.to_string(),
diagnostics: diagnostics.cloned(),
};
// GRCOV_STOP_COVERAGE
let output: PutHostCommandAcknowledgementOutput =
self.call("PutHostCommandAcknowledgement", &input)?;
Ok(output.command_status)
// GRCOV_BEGIN_COVERAGE
}

/// Get deployment specification.
Expand Down Expand Up @@ -136,13 +132,10 @@ impl Client {
estimated_completion_time: estimated_completion_time.map(String::from),
diagnostics: diagnostics.cloned(),
};
// GRCOV_STOP_COVERAGE
let output: PostHostCommandUpdateOutput = self.call("PostHostCommandUpdate", &input)?;
Ok(output.command_status)
// GRCOV_BEGIN_COVERAGE
}

// GRCOV_STOP_COVERAGE
fn call<I: serde::Serialize, O: serde::de::DeserializeOwned>(
&self,
operation: &str,
Expand Down Expand Up @@ -236,7 +229,6 @@ impl Client {
Ok(())
}
}
// GRCOV_BEGIN_COVERAGE

/// Builder for [`Client`].
#[derive(Debug)]
Expand Down
3 changes: 3 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.98"
components = ["rustfmt", "clippy"]
6 changes: 4 additions & 2 deletions src/application_specification/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,9 +400,11 @@ mod tests {
#[test]
fn from_file_success() {
use std::io::Write;
let mut file = std::fs::File::create("/tmp/test_appspec_success.yaml").unwrap();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test_appspec_success.yaml");
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(b"version: 0.0\nos: linux\n").unwrap();
let result = AppSpec::from_file("/tmp/test_appspec_success.yaml");
let result = AppSpec::from_file(&path);
assert!(result.is_ok());
}

Expand Down
6 changes: 0 additions & 6 deletions src/aws_clients/codedeploy_command_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,11 @@ struct RefreshableInner {
}

impl std::fmt::Debug for RefreshableInner {
// GRCOV_STOP_COVERAGE
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RefreshableInner")
.field("credential_expiry", &self.credential_expiry)
.finish_non_exhaustive()
}
// GRCOV_BEGIN_COVERAGE
}

#[derive(Debug)]
Expand Down Expand Up @@ -233,7 +231,6 @@ impl CodeDeployCommandClient {
&self.throttle_gate
}

// GRCOV_STOP_COVERAGE
/// Refresh credentials if they are near expiry.
///
/// Rebuilds the inner HTTP client with fresh credentials. For `IamSession`,
Expand Down Expand Up @@ -453,7 +450,6 @@ impl CodeDeployCommandClient {
Ok(())
})
}
// GRCOV_BEGIN_COVERAGE

/// The configured region.
#[must_use]
Expand Down Expand Up @@ -494,7 +490,6 @@ fn to_aws_credentials(creds: &Credentials) -> Result<AwsCredentials, CodeDeployC
// `credential_expiry`; `refresh_if_needed()` re-fetches before expiry.
// Returns Err if IMDS is unreachable — the agent cannot start without
// valid credentials.
// GRCOV_STOP_COVERAGE — requires IMDS endpoint
use crate::aws_clients::imds;
match imds::fetch_credentials() {
Ok(aws_creds) => {
Expand All @@ -503,7 +498,6 @@ fn to_aws_credentials(creds: &Credentials) -> Result<AwsCredentials, CodeDeployC
},
Err(e) => Err(CodeDeployClientError::ImdsUnavailable(e.to_string())),
}
// GRCOV_BEGIN_COVERAGE
},
CredentialMode::IamSession { credentials_file } => {
use crate::aws_clients::file_credentials;
Expand Down
2 changes: 1 addition & 1 deletion src/aws_clients/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl Credentials {
mode: CredentialMode::InstanceProfile,
});
},
Err(e) => return Err(e.into()), // GRCOV_IGNORE_LINE
Err(e) => return Err(e.into()),
};
let contents = crate::config::strip_symbol_keys(&contents);
let config: OnPremisesConfigFile = serde_yaml::from_str(&contents).map_err(|e| {
Expand Down
Loading
Loading