Skip to content
Merged
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
env:
CARGO_TERM_COLOR: always
BUILD_PROFILE: debug
WHITAKER_INSTALLER_VERSION: '0.2.5'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Install system dependencies
Expand All @@ -30,6 +31,24 @@ jobs:
**/*.md
!**/target/**
!**/dist/**
- name: Cache whitaker-installer
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/bin/whitaker-installer
~/.cache/cargo-binstall
key: whitaker-installer-${{ runner.os }}-${{ runner.arch }}-${{ env.WHITAKER_INSTALLER_VERSION }}
- name: Install the Whitaker Dylint suite
run: |
if ! command -v whitaker-installer >/dev/null 2>&1; then
if cargo binstall --version >/dev/null 2>&1; then
cargo binstall --no-confirm --locked "whitaker-installer@${WHITAKER_INSTALLER_VERSION}"
else
echo "cargo-binstall unavailable; building whitaker-installer from crates.io"
cargo install --locked whitaker-installer --version "${WHITAKER_INSTALLER_VERSION}"
fi
fi
whitaker-installer
- name: Lint
run: make lint
- name: Test and Measure Coverage
Expand Down
96 changes: 96 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "fmt"] }
dashmap = "6.2.1"
metrics = "0.22.4"
metrics-exporter-prometheus = "0.13.1"
cap-std = "4.0.2"

[dev-dependencies]
assert_cmd = "2.0.16"
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ BUILD_JOBS ?=
CLIPPY_FLAGS ?= --all-targets --all-features -- -D warnings
MDLINT ?= markdownlint-cli2
NIXIE ?= nixie
WHITAKER ?= whitaker

build: target/debug/$(APP) ## Build debug binary
release: target/release/$(APP) ## Build release binary
Expand All @@ -21,8 +22,9 @@ test: ## Run tests with warnings treated as errors
target/%/$(APP): ## Build binary in debug or release mode
$(CARGO) build $(BUILD_JOBS) $(if $(findstring release,$(@)),--release) --bin $(APP)

lint: ## Run Clippy with warnings denied
lint: ## Run Clippy and the Whitaker Dylint suite with warnings denied
$(CARGO) clippy --all-targets --all-features -- -D warnings
RUSTFLAGS="-D warnings" $(WHITAKER) --all -- --all-targets --all-features
Comment thread
leynos marked this conversation as resolved.

typecheck: ## Run cargo check for fast no-link verification
$(CARGO) check --all-targets $(BUILD_JOBS)
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,22 @@ directories.
Fast feedback is available through:

- `cargo fmt` for formatting checks.
- `cargo clippy --all-targets --all-features -- -D warnings` for linting.
- `make lint` for linting, which runs Clippy
(`cargo clippy --all-targets --all-features -- -D warnings`) followed by the
[Whitaker](https://github.com/leynos/whitaker) Dylint suite with warnings
denied.
- `cargo test` for unit tests built with `rstest` and `tokio`.

Linting requires the Whitaker suite. Install it with
[`whitaker-installer`](https://github.com/leynos/whitaker):

```bash
cargo binstall --no-confirm --locked whitaker-installer # or: cargo install --locked whitaker-installer
whitaker-installer
```

This provisions the pinned toolchain, `cargo-dylint`, and the `whitaker`
wrapper used by `make lint`.

The rate limiter depends on the `mockable` clock abstraction, enabling
deterministic control of timestamps in the test suite.
5 changes: 4 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ fn ensure_directory(path: &Path) -> Result<()> {
path.display()
));
}
std::fs::create_dir_all(path)
// Creating the store root from operator-supplied configuration is the one
// ambient filesystem operation the server performs; cap-std makes that
// ambient authority explicit.
cap_std::fs::Dir::create_ambient_dir_all(path, cap_std::ambient_authority())
.with_context(|| format!("failed to create store root {}", path.display()))?;
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions src/framing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub fn trim_line_endings(line: &str) -> &str {

#[cfg(test)]
mod tests {
//! Unit tests for finger response framing and line endings.
use super::*;
use rstest::rstest;

Expand Down
1 change: 1 addition & 0 deletions src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl fmt::Display for HostName {

#[cfg(test)]
mod tests {
//! Unit tests for username and hostname validation.
use super::*;
use rstest::rstest;

Expand Down
12 changes: 10 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ use server::FingerServer;
use storage::ObjectStoreUserStore;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<()> {
fn main() -> Result<()> {
install_tracing();

// Build the runtime explicitly so runtime construction errors propagate
// instead of panicking inside the `#[tokio::main]` expansion.
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
runtime.block_on(run())
}

async fn run() -> Result<()> {
let cli = CliOptions::parse();
let config = ServerConfig::from_cli(cli)?;
let metrics_endpoint = telemetry::install_metrics(config.metrics_listen).await?;
Expand Down
1 change: 1 addition & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl FingerQuery {

#[cfg(test)]
mod tests {
//! Unit tests for finger query parsing.
use super::*;
use rstest::rstest;

Expand Down
1 change: 1 addition & 0 deletions src/rate_limit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ fn chrono_duration(window: Duration) -> ChronoDuration {

#[cfg(test)]
mod tests {
//! Unit tests for the per-client rate limiter.
use super::*;
use std::sync::Mutex as StdMutex;

Expand Down
1 change: 1 addition & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ fn render_message(message: &str) -> Vec<u8> {

#[cfg(test)]
mod tests {
//! Unit tests for finger server request handling.
use super::*;
use crate::identity::{HostName, Username};
use crate::rate_limit::RateLimitSettings;
Expand Down
16 changes: 10 additions & 6 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,20 @@ fn trim_slashes(input: impl AsRef<str>) -> String {

#[cfg(test)]
mod tests {
//! Behavioural tests for the object-store backed user repository.
use super::*;
use crate::identity::Username;
use anyhow::{Result, anyhow};
use cap_std::fs::Dir;
use object_store::local::LocalFileSystem;
use rstest::rstest;
use tempfile::TempDir;

fn write_file(path: &std::path::Path, contents: &str) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
fn write_file(root: &Dir, relative: &str, contents: &str) -> Result<()> {
if let Some((parent, _)) = relative.rsplit_once('/') {
root.create_dir_all(parent)?;
}
std::fs::write(path, contents)?;
root.write(relative, contents)?;
Ok(())
}

Expand Down Expand Up @@ -224,15 +226,17 @@ mod tests {
async fn load_user_behaviour(#[case] case: LoadCase) -> Result<()> {
let tmp = TempDir::new()?;
let root = tmp.path();
let root_dir = Dir::open_ambient_dir(root, cap_std::ambient_authority())?;

if let Some(user) = &case.existing_user {
// mirror production repository layout so we exercise the full IO path
write_file(
&root.join(format!("profiles/{}.toml", user.username)),
&root_dir,
&format!("profiles/{}.toml", user.username),
user.profile,
)?;
if let Some(plan) = user.plan {
write_file(&root.join(format!("plans/{}.plan", user.username)), plan)?;
write_file(&root_dir, &format!("plans/{}.plan", user.username), plan)?;
}
}

Expand Down
Loading
Loading