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
6 changes: 0 additions & 6 deletions .config/nextest.toml

This file was deleted.

30 changes: 3 additions & 27 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32

- name: Install cargo-nextest
uses: taiki-e/install-action@v2
with:
Expand All @@ -42,32 +45,5 @@ jobs:
- name: Check artifact recipe scripts
run: shellcheck release/artifacts/recipes/common.sh release/artifacts/recipes/php/*.sh release/artifacts/recipes/composer/*.sh release/artifacts/recipes/redis/*.sh release/artifacts/recipes/mysql/*.sh release/artifacts/recipes/postgres/*.sh release/artifacts/recipes/mailpit/*.sh release/artifacts/recipes/rustfs/*.sh

- name: Validate artifact recipe metadata and fixtures
run: |
rm -rf /tmp/pv-recipe-fixtures
cargo run -p pv-release -- generate-recipe-fixtures \
--php release/artifacts/recipes/php/tracks.toml \
--composer release/artifacts/recipes/composer/composer.toml \
--redis release/artifacts/recipes/redis/recipe.toml \
--mysql release/artifacts/recipes/mysql/recipe.toml \
--postgres release/artifacts/recipes/postgres/recipe.toml \
--mailpit release/artifacts/recipes/mailpit/recipe.toml \
--rustfs release/artifacts/recipes/rustfs/recipe.toml \
--archives /tmp/pv-recipe-fixtures/archives \
--records /tmp/pv-recipe-fixtures/records \
--pv-commit "$(git rev-parse HEAD)" \
--build-run-id ci
cargo run -p pv-release -- generate-manifest \
--records /tmp/pv-recipe-fixtures/records \
--revocations release/artifacts/revocations \
--defaults release/artifacts/default-tracks.toml \
--output /tmp/pv-recipe-fixtures/manifest.json \
--base-url https://artifacts.example.test

- name: Run tests
run: cargo nextest run --workspace --all-features --locked

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore rustdoc warning coverage

In the CI workflow I inspected, this is now the final Rust validation step; the previous cargo doc --workspace --all-features --no-deps --locked with RUSTDOCFLAGS=-D warnings was deleted. I checked cargo doc --help, which describes it as building package documentation, so PRs that introduce rustdoc-only warnings such as broken intra-doc links can now merge because the remaining test/clippy steps do not build docs. Please keep this step or replace it with an equivalent cheaper rustdoc check if the goal is no coverage loss.

Useful? React with 👍 / 👎.


- name: Build docs
run: cargo doc --workspace --all-features --no-deps --locked
env:
RUSTDOCFLAGS: -D warnings
44 changes: 26 additions & 18 deletions crates/daemon/src/managed_resources/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ use std::os::unix::fs::PermissionsExt;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use crate::{
DaemonError, ProcessSpec, ProcessSupervisor, ReadinessCheck,
managed_resources::{ManagedResourceRuntimeAdapter, ManagedResourceRuntimeContext},
reconciliation::{ReconciliationQueue, ReconciliationScope},
};
use anyhow::{Result, bail};
use camino::Utf8Path;
use camino_tempfile::tempdir;
Expand All @@ -19,12 +24,6 @@ use state::{
StateError,
};

use crate::{
DaemonError, ProcessSpec, ProcessSupervisor, ReadinessCheck,
managed_resources::{ManagedResourceRuntimeAdapter, ManagedResourceRuntimeContext},
reconciliation::{ReconciliationQueue, ReconciliationScope},
};

const FAKE_MAILPIT_TRACK: &str = "1.0";
const FAKE_MAILPIT_NEXT_TRACK: &str = "1.1";
const FAKE_MAILPIT_ARTIFACT_VERSION: &str = "1.0.0-pv1";
Expand Down Expand Up @@ -4585,16 +4584,12 @@ done
}

fn fast_exit_fake_mailpit_script() -> &'static str {
r#"#!/bin/sh
set -eu

dashboard_port="$2"

python3 - "$dashboard_port" <<'PY'
r#"#!/usr/bin/env python3
import http.server
import os
import sys


class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
Expand All @@ -4606,9 +4601,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, _format, *_args):
pass

server = http.server.ThreadingHTTPServer(("127.0.0.1", int(sys.argv[1])), Handler)

server = http.server.ThreadingHTTPServer(("127.0.0.1", int(sys.argv[2])), Handler)
server.serve_forever()
PY
"#
}

Expand Down Expand Up @@ -4874,6 +4869,7 @@ import signal
import shlex
import socketserver
import sys
import threading

def redis_config(argv):
port = None
Expand Down Expand Up @@ -4915,9 +4911,16 @@ class RedisPingHandler(socketserver.BaseRequestHandler):

class RedisServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
allow_reuse_address = True
daemon_threads = True

shutdown_requested = threading.Event()


def stop(_signum, _frame):
server.shutdown()
if shutdown_requested.is_set():
return
shutdown_requested.set()
threading.Thread(target=server.shutdown, daemon=True).start()
Comment thread
pullfrog[bot] marked this conversation as resolved.

port, data_dir = redis_config(sys.argv[1:])
if data_dir:
Expand Down Expand Up @@ -5083,16 +5086,21 @@ class Server(http.server.ThreadingHTTPServer):
api = Server(split_address(api_address), RustfsHandler)
console = Server(split_address(console_address), ConsoleHandler)

shutdown_requested = threading.Event()


def stop(_signum, _frame):
api.shutdown()
console.shutdown()
sys.exit(0)
if shutdown_requested.is_set():
return
shutdown_requested.set()
threading.Thread(target=api.shutdown, daemon=True).start()

signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)

threading.Thread(target=console.serve_forever, daemon=True).start()
api.serve_forever()
console.shutdown()
PY
"#
.replace("__PV_REJECT_S3__", reject_s3)
Expand Down
55 changes: 55 additions & 0 deletions crates/daemon/tests/supervisor_foundation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,61 @@ async fn supervisor_verifies_and_adopts_owned_runtime_metadata() -> Result<()> {
Ok(())
}

#[cfg(target_os = "macos")]
#[tokio::test]
async fn supervisor_verifies_owned_python_shebang_script() -> Result<()> {
let tempdir = tempdir()?;
let paths = PvPaths::for_home(tempdir.path().join("home"));
state::fs::ensure_layout(&paths)?;
let runtime = paths.run().join("owned-python-runtime");
state::fs::write_sensitive_file(
&runtime,
r#"#!/usr/bin/env python3
import signal
import sys


def stop(_signum, _frame):
sys.exit(0)


if sys.argv[1:] != ["1025", "8025"]:
sys.exit(2)

signal.signal(signal.SIGTERM, stop)
signal.pause()
"#,
)?;
set_executable(&runtime)?;

let supervisor = ProcessSupervisor::new(paths.clone());
let spec = process_spec(
&paths,
"owned-python-runtime",
runtime,
vec!["1025".to_string(), "8025".to_string()],
);
let process = supervisor.start(spec.clone()).await?;
let pid = process.pid();
let ownership = timeout(Duration::from_secs(1), async {
loop {
if let Some(owned) = supervisor.verify_ownership(&spec)? {
return Ok::<_, daemon::DaemonError>(owned);
}

sleep(Duration::from_millis(10)).await;
}
})
.await;

process.stop(Duration::from_secs(1)).await?;
let owned = ownership??;

assert_eq!(owned.pid(), pid);

Ok(())
}

#[tokio::test]
async fn supervisor_rejects_owned_runtime_when_private_environment_changes() -> Result<()> {
let tempdir = tempdir()?;
Expand Down
89 changes: 67 additions & 22 deletions crates/pv-release/tests/recipe_fixtures.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::{Result, bail};
use std::process::Output;

use anyhow::{Context, Result, bail};
use camino::Utf8Path;
use camino_tempfile::tempdir;
use flate2::read::GzDecoder;
Expand All @@ -11,6 +13,12 @@ use pv_release::record::{ReleaseRecord, load_release_records};
use resources::ArtifactManifest;
use tar::Archive;

#[expect(
clippy::disallowed_types,
reason = "release tooling CLI tests execute the pv-release binary"
)]
type StdCommand = std::process::Command;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct ArchiveRoot {
resource: String,
Expand Down Expand Up @@ -39,7 +47,7 @@ fn recipe_fixture_generation_validates_archives_records_and_manifest() -> Result
let tempdir = tempdir()?;
let archives = tempdir.path().join("archives");
let records = tempdir.path().join("records");
let revocations = tempdir.path().join("revocations");
let revocations = workspace_root.join("release/artifacts/revocations");
let manifest = tempdir.path().join("manifest.json");
let php = workspace_root.join("release/artifacts/recipes/php/tracks.toml");
let composer = workspace_root.join("release/artifacts/recipes/composer/composer.toml");
Expand All @@ -50,27 +58,37 @@ fn recipe_fixture_generation_validates_archives_records_and_manifest() -> Result
let rustfs = workspace_root.join("release/artifacts/recipes/rustfs/recipe.toml");
let defaults = workspace_root.join("release/artifacts/default-tracks.toml");

create_dir_all(&revocations)?;
let redis_recipe = BackingRecipe::load(&redis, BackingRecipeKind::Redis)?;
let Some(redis_track) = redis_recipe.tracks().first() else {
bail!("committed Redis recipe should define a track");
};
let redis_upstream_version = redis_track.upstream_version().to_string();
generate_recipe_fixtures_with_backing(
&php,
&composer,
&[
(BackingRecipeKind::Redis, redis.clone()),
(BackingRecipeKind::Mysql, mysql),
(BackingRecipeKind::Postgres, postgres),
(BackingRecipeKind::Mailpit, mailpit),
(BackingRecipeKind::Rustfs, rustfs),
],
&archives,
&records,
let fixture_output = run_pv_release(&[
"generate-recipe-fixtures",
"--php",
php.as_str(),
"--composer",
composer.as_str(),
"--redis",
redis.as_str(),
"--mysql",
mysql.as_str(),
"--postgres",
postgres.as_str(),
"--mailpit",
mailpit.as_str(),
"--rustfs",
rustfs.as_str(),
"--archives",
archives.as_str(),
"--records",
records.as_str(),
"--pv-commit",
"0123456789abcdef0123456789abcdef01234567",
"--build-run-id",
"local-test",
)?;
])?;
assert_command_success(&fixture_output, "generate-recipe-fixtures")?;
let archive_roots = generated_archive_roots(&archives, &records)?;
assert_eq!(
archive_roots,
Expand Down Expand Up @@ -216,13 +234,20 @@ fn recipe_fixture_generation_validates_archives_records_and_manifest() -> Result
),
],
);
generate_manifest_file_with_defaults(
&records,
&revocations,
Some(&defaults),
&manifest,
let manifest_output = run_pv_release(&[
"generate-manifest",
"--records",
records.as_str(),
"--revocations",
revocations.as_str(),
"--defaults",
defaults.as_str(),
"--output",
manifest.as_str(),
"--base-url",
"https://artifacts.example.test",
)?;
])?;
assert_command_success(&manifest_output, "generate-manifest")?;

let manifest_json = read_to_string(&manifest)?;
ArtifactManifest::parse(&manifest_json)?;
Expand Down Expand Up @@ -428,6 +453,26 @@ fn archive_entries(path: &Utf8Path) -> Result<Vec<String>> {
Ok(entries)
}

fn run_pv_release(args: &[&str]) -> Result<Output> {
StdCommand::new(env!("CARGO_BIN_EXE_pv-release"))
.args(args)
.output()
.context("failed to execute pv-release")
}

fn assert_command_success(output: &Output, label: &str) -> Result<()> {
if output.status.success() {
return Ok(());
}

bail!(
"{label} failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}

#[expect(
clippy::disallowed_methods,
reason = "release tooling tests create local revocation fixture directories"
Expand Down
Loading
Loading