mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
test(archive): start the archive relay through the shared fixture
The archive service tests started ngit-grasp with a private helper that reserved a port, released the reservation, and then spawned the relay. Between the release and the child's bind, another test in the same process, or another test binary, could bind the same port. The failure surfaced under concurrent load as "connection refused" after readiness, because the readiness probe had reached whichever process won the port. Use `TestRelay::start_with_archive_grasp_services`, which transfers the bound listener into the child through the test listener handoff and never releases the address. The tests keep their announcement and repository assertions and now stop the relay through the fixture, which also captures its log. This removes the last `PortReservation::release` call in the test suite. Validation: measured against master with the same binaries, 20 unloaded runs and 30 samples as three concurrent instances under CPU spinners; see the pull request description. Assisted-by: Claude Fable 5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
cdf9567561
commit
fe1793d707
@@ -35,86 +35,8 @@ use common::{
|
|||||||
wait_for_sync_connection, CommitVariant, TestRelay,
|
wait_for_sync_connection, CommitVariant, TestRelay,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::{Child, Command, Stdio};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Helper to start a relay with archive_grasp_services configuration
|
|
||||||
///
|
|
||||||
/// This is a specialized version of TestRelay::start_on_reservation_with_archive_and_sync
|
|
||||||
/// that adds the NGIT_ARCHIVE_GRASP_SERVICES environment variable.
|
|
||||||
async fn start_relay_with_grasp_services(services: &str) -> (Child, String, PathBuf) {
|
|
||||||
// Reserve a port and hold the listener bound until just before the
|
|
||||||
// subprocess spawns — mirrors the pattern in `TestRelay`. Without
|
|
||||||
// this hold, a parallel test in this process could be handed the
|
|
||||||
// same port between the kernel-assigned bind and ngit-grasp's own
|
|
||||||
// bind, producing a flaky `Address already in use` failure.
|
|
||||||
let reservation = port::reserve_port();
|
|
||||||
let relay_port = reservation.port();
|
|
||||||
let bind_address = format!("127.0.0.1:{}", relay_port);
|
|
||||||
let url = format!("ws://127.0.0.1:{}", relay_port);
|
|
||||||
|
|
||||||
// Create temporary directory for git repositories
|
|
||||||
let git_data_dir = tempfile::tempdir().expect("Failed to create temporary git data directory");
|
|
||||||
|
|
||||||
// Use the built binary directly
|
|
||||||
let binary_path = std::env::current_exe()
|
|
||||||
.expect("Failed to get current exe")
|
|
||||||
.parent()
|
|
||||||
.expect("Failed to get parent dir")
|
|
||||||
.parent()
|
|
||||||
.expect("Failed to get grandparent dir")
|
|
||||||
.join("ngit-grasp");
|
|
||||||
|
|
||||||
// Give the relay a stable signing identity for NIP-11 and NIP-42.
|
|
||||||
let test_keys = nostr_sdk::prelude::Keys::generate();
|
|
||||||
let test_nsec = test_keys
|
|
||||||
.secret_key()
|
|
||||||
.to_bech32()
|
|
||||||
.expect("Failed to generate test nsec");
|
|
||||||
|
|
||||||
// Start the relay process with archive_grasp_services
|
|
||||||
let mut cmd = Command::new(&binary_path);
|
|
||||||
cmd.env("NGIT_BIND_ADDRESS", &bind_address)
|
|
||||||
.env("NGIT_DOMAIN", &bind_address)
|
|
||||||
.env("NGIT_GIT_DATA_PATH", git_data_dir.path())
|
|
||||||
.env("NGIT_DATABASE_BACKEND", "memory")
|
|
||||||
.env("NGIT_RELAY_OWNER_NSEC", &test_nsec)
|
|
||||||
.env("NGIT_ARCHIVE_GRASP_SERVICES", services)
|
|
||||||
.env(
|
|
||||||
"RUST_LOG",
|
|
||||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
|
|
||||||
)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null());
|
|
||||||
|
|
||||||
// Release the reservation immediately before spawn so the subprocess
|
|
||||||
// can bind the port. See `tests/common/port.rs` for the rationale.
|
|
||||||
let _ = reservation.release();
|
|
||||||
|
|
||||||
let process = cmd.spawn().expect("Failed to start relay process");
|
|
||||||
|
|
||||||
// Store git data path for test assertions
|
|
||||||
let git_data_path = git_data_dir.path().to_path_buf();
|
|
||||||
|
|
||||||
// Wait for relay to be ready
|
|
||||||
wait_for_relay_ready(relay_port).await;
|
|
||||||
|
|
||||||
(process, url, git_data_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for the relay to be ready to accept connections.
|
|
||||||
///
|
|
||||||
/// A successful TCP connect only proves the socket is bound: the accept loop
|
|
||||||
/// and HTTP service may still be coming up. Poll the relay's HTTP handler
|
|
||||||
/// instead, bounded by a deadline, so the immediately-following WebSocket
|
|
||||||
/// client is racing a relay that has already served a request.
|
|
||||||
async fn wait_for_relay_ready(port: u16) {
|
|
||||||
common::relay::wait_for_http_ready(port, Duration::from_secs(10))
|
|
||||||
.await
|
|
||||||
.expect("Relay should become ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test that announcements with matching GRASP service domains are accepted.
|
/// Test that announcements with matching GRASP service domains are accepted.
|
||||||
///
|
///
|
||||||
/// Scenario:
|
/// Scenario:
|
||||||
@@ -123,8 +45,9 @@ async fn wait_for_relay_ready(port: u16) {
|
|||||||
/// 3. Verify announcement is accepted (repository is created)
|
/// 3. Verify announcement is accepted (repository is created)
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_archive_accepts_matching_grasp_service() {
|
async fn test_archive_accepts_matching_grasp_service() {
|
||||||
let (mut process, url, git_data_path) =
|
let relay = TestRelay::start_with_archive_grasp_services("git.example.com").await;
|
||||||
start_relay_with_grasp_services("git.example.com").await;
|
let url = relay.url().to_string();
|
||||||
|
let git_data_path = relay.git_data_path().clone();
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let identifier = "test-repo";
|
let identifier = "test-repo";
|
||||||
|
|
||||||
@@ -168,8 +91,7 @@ async fn test_archive_accepts_matching_grasp_service() {
|
|||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
client.disconnect().await;
|
client.disconnect().await;
|
||||||
let _ = process.kill();
|
relay.stop().await;
|
||||||
let _ = process.wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that announcements with non-matching GRASP service domains are rejected.
|
/// Test that announcements with non-matching GRASP service domains are rejected.
|
||||||
@@ -180,8 +102,9 @@ async fn test_archive_accepts_matching_grasp_service() {
|
|||||||
/// 3. Verify announcement is rejected (repository is NOT created)
|
/// 3. Verify announcement is rejected (repository is NOT created)
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_archive_rejects_non_matching_grasp_service() {
|
async fn test_archive_rejects_non_matching_grasp_service() {
|
||||||
let (mut process, url, git_data_path) =
|
let relay = TestRelay::start_with_archive_grasp_services("git.example.com").await;
|
||||||
start_relay_with_grasp_services("git.example.com").await;
|
let url = relay.url().to_string();
|
||||||
|
let git_data_path = relay.git_data_path().clone();
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let identifier = "test-repo";
|
let identifier = "test-repo";
|
||||||
|
|
||||||
@@ -225,8 +148,7 @@ async fn test_archive_rejects_non_matching_grasp_service() {
|
|||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
client.disconnect().await;
|
client.disconnect().await;
|
||||||
let _ = process.kill();
|
relay.stop().await;
|
||||||
let _ = process.wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that multiple configured GRASP services work correctly.
|
/// Test that multiple configured GRASP services work correctly.
|
||||||
@@ -239,8 +161,10 @@ async fn test_archive_rejects_non_matching_grasp_service() {
|
|||||||
/// 5. Verify it is rejected
|
/// 5. Verify it is rejected
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_archive_multiple_grasp_services() {
|
async fn test_archive_multiple_grasp_services() {
|
||||||
let (mut process, url, git_data_path) =
|
let relay =
|
||||||
start_relay_with_grasp_services("git.example.com,gitlab.example.org").await;
|
TestRelay::start_with_archive_grasp_services("git.example.com,gitlab.example.org").await;
|
||||||
|
let url = relay.url().to_string();
|
||||||
|
let git_data_path = relay.git_data_path().clone();
|
||||||
|
|
||||||
// Test first service (git.example.com)
|
// Test first service (git.example.com)
|
||||||
let keys1 = Keys::generate();
|
let keys1 = Keys::generate();
|
||||||
@@ -362,8 +286,7 @@ async fn test_archive_multiple_grasp_services() {
|
|||||||
client1.disconnect().await;
|
client1.disconnect().await;
|
||||||
client2.disconnect().await;
|
client2.disconnect().await;
|
||||||
client3.disconnect().await;
|
client3.disconnect().await;
|
||||||
let _ = process.kill();
|
relay.stop().await;
|
||||||
let _ = process.wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that archive_read_only mode creates bare git repositories and syncs data
|
/// Test that archive_read_only mode creates bare git repositories and syncs data
|
||||||
|
|||||||
Reference in New Issue
Block a user