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
113 changes: 113 additions & 0 deletions .github/skills/dev/testing/public-trackers-for-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
name: public-trackers-for-testing
description: Public tracker targets for manual testing and debugging of tracker clients. Use when validating announce/scrape behavior against live services, comparing local vs public behavior, or diagnosing network timeouts. Triggers on "public tracker", "test against demo tracker", "debug tracker timeout", or "which tracker should I use".
metadata:
author: torrust
version: "1.0"
---

# Public Trackers for Testing

## Skill Links

This skill depends on these artifacts. If any of them change, review this skill.

- `console/tracker-client/src/console/clients/udp/app.rs`
- `console/tracker-client/src/console/clients/http/app.rs`
- `.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md`

Use the marker `skill-link: public-trackers-for-testing` in affected artifacts.

## Purpose

Use this skill to choose reliable public tracker endpoints for manual verification and debugging.

It provides:

- preferred endpoint order
- copy-paste test commands
- timeout triage and fallback workflow

## Preferred Target Order

When testing against public services, use this order:

1. Tracker demo (newer, usually lower load)
2. Index+Tracker demo (older, can be busy)
3. Local tracker fallback for deterministic checks

## Public Endpoints

### Tracker Demo (preferred)

Repository: <https://github.com/torrust/torrust-tracker-demo>

- HTTP: `https://http1.torrust-tracker-demo.com:443/announce`
- HTTP: `https://http1.torrust-tracker-demo.com:443`
- UDP: `udp://udp1.torrust-tracker-demo.com:6969/announce`

### Index+Tracker Demo (secondary)

Repository: <https://github.com/torrust/torrust-demo>

- HTTP: `https://tracker.torrust-demo.com/announce`
Comment thread
josecelano marked this conversation as resolved.
- HTTP: `https://tracker.torrust-demo.com`
- UDP: `udp://tracker.torrust-demo.com:6969/announce`

## Quick Commands

Use a test info hash:

```bash
INFO_HASH=000620bbc6c52d5a96d98f6c0f1dfa523a40df82
```

### UDP scrape (preferred public demo)

```bash
cargo run -q -p torrust-tracker-client --bin udp_tracker_client scrape \
udp://udp1.torrust-tracker-demo.com:6969/scrape \
"$INFO_HASH" \
--format pretty
```

### UDP announce (preferred public demo)

```bash
cargo run -q -p torrust-tracker-client --bin udp_tracker_client announce \
udp://udp1.torrust-tracker-demo.com:6969/announce \
"$INFO_HASH" \
--format compact
```

### HTTP announce (preferred public demo)

```bash
cargo run -q -p torrust-tracker-client --bin http_tracker_client announce \
https://http1.torrust-tracker-demo.com:443 \
"$INFO_HASH"
```

## Timeout Triage

If a public target times out:

1. Retry once against the same target.
2. Retry against the other public demo.
3. If both fail, run locally and verify behavior deterministically.

Do not assume client regression from a single public timeout.

## Local Fallback

Use this workflow when public trackers are unavailable or overloaded:

- `.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md`

Then re-run the same client command against `127.0.0.1`.

## Notes

- Public demo load varies over time.
- Trackers may contain existing swarm state, so results can differ from clean local runs.
- Prefer local checks for acceptance criteria that require deterministic values.
1 change: 1 addition & 0 deletions console/tracker-client/src/console/clients/http/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! HTTP Tracker client:
//! skill-link: public-trackers-for-testing
//!
//! Examples:
//!
Expand Down
45 changes: 37 additions & 8 deletions console/tracker-client/src/console/clients/udp/app.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
//! UDP Tracker client:
//! skill-link: public-trackers-for-testing
//!
//! Examples:
//!
//! Announce request (minimal):
//!
//! ```text
//! cargo run --bin udp_tracker_client announce 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
//! cargo run --bin udp_tracker_client announce 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422
//! ```
//!
//! Announce request (pretty JSON output):
//!
//! ```text
//! cargo run --bin udp_tracker_client announce \
//! 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 \
//! --format pretty
//! ```
//!
//! Announce request (all optional parameters):
Expand Down Expand Up @@ -41,7 +50,15 @@
//! Scrape request:
//!
//! ```text
//! cargo run --bin udp_tracker_client scrape 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
//! cargo run --bin udp_tracker_client scrape 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422
//! ```
//!
//! Scrape request (pretty JSON output):
//!
//! ```text
//! cargo run --bin udp_tracker_client scrape \
//! 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 \
//! --format pretty
//! ```
//!
//! Scrape response:
Expand Down Expand Up @@ -73,8 +90,8 @@
//! You can use an URL with instead of the socket address. For example:
//!
//! ```text
//! cargo run --bin udp_tracker_client scrape udp://localhost:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
//! cargo run --bin udp_tracker_client scrape udp://localhost:6969/scrape 9c38422213e30bff212b30c360d26f9a02136422 | jq
//! cargo run --bin udp_tracker_client scrape udp://localhost:6969 9c38422213e30bff212b30c360d26f9a02136422
//! cargo run --bin udp_tracker_client scrape udp://localhost:6969/scrape 9c38422213e30bff212b30c360d26f9a02136422
//! ```
//!
//! The protocol (`udp://`) in the URL is mandatory. The path (`\scrape`) is optional. It always uses `\scrape`.
Expand Down Expand Up @@ -117,6 +134,12 @@ impl From<CliAnnounceEvent> for AnnounceEvent {
}
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum OutputFormat {
Compact,
Pretty,
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
Expand Down Expand Up @@ -149,12 +172,16 @@ enum Command {
key: Option<i32>,
#[arg(long = "peers-wanted")]
peers_wanted: Option<i32>,
#[arg(long, value_enum, default_value_t = OutputFormat::Compact)]
format: OutputFormat,
},
Scrape {
#[arg(value_parser = parse_socket_addr)]
tracker_socket_addr: SocketAddr,
#[arg(value_parser = parse_info_hash, num_args = 1..=74, value_delimiter = ' ')]
info_hashes: Vec<TorrustInfoHash>,
#[arg(long, value_enum, default_value_t = OutputFormat::Compact)]
format: OutputFormat,
},
}

Expand All @@ -168,7 +195,7 @@ pub async fn run() -> anyhow::Result<()> {

let args = Args::parse();

let response = match args.command {
let (response, output_format) = match args.command {
Command::Announce {
tracker_socket_addr: remote_addr,
info_hash,
Expand All @@ -181,6 +208,7 @@ pub async fn run() -> anyhow::Result<()> {
peer_id,
key,
peers_wanted,
format,
} => {
let params = AnnounceParams {
event: event.map(Into::into),
Expand All @@ -202,16 +230,17 @@ pub async fn run() -> anyhow::Result<()> {
key,
peers_wanted,
};
handle_announce(remote_addr, &info_hash, &params).await?
(handle_announce(remote_addr, &info_hash, &params).await?, format)
}
Command::Scrape {
tracker_socket_addr: remote_addr,
info_hashes,
} => handle_scrape(remote_addr, &info_hashes).await?,
format,
} => (handle_scrape(remote_addr, &info_hashes).await?, format),
};

let response: SerializableResponse = response.into();
let response_json = response.to_json_string()?;
let response_json = response.to_json_string(matches!(output_format, OutputFormat::Pretty))?;

print!("{response_json}");

Expand Down
51 changes: 48 additions & 3 deletions console/tracker-client/src/console/clients/udp/responses/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,59 @@ pub trait ToJson {
///
/// Will return an error if serialization fails.
///
fn to_json_string(&self) -> anyhow::Result<String>
fn to_json_string(&self, pretty: bool) -> anyhow::Result<String>
where
Self: Serialize,
{
let pretty_json = serde_json::to_string_pretty(self).context("response JSON serialization")?;
let json = if pretty {
serde_json::to_string_pretty(self).context("response JSON pretty serialization")?
} else {
serde_json::to_string(self).context("response JSON compact serialization")?
};

Ok(pretty_json)
Ok(json)
}
}

impl ToJson for SerializableResponse {}

#[cfg(test)]
mod tests {
use serde::Serialize;

use super::ToJson;

#[derive(Serialize)]
struct SampleResponse {
transaction_id: i32,
seeders: i32,
}

impl ToJson for SampleResponse {}

#[test]
fn it_should_serialize_compact_json_when_pretty_is_false() {
let response = SampleResponse {
transaction_id: 10,
seeders: 2,
};

let json = response.to_json_string(false).expect("it should serialize compact JSON");

assert_eq!(json, "{\"transaction_id\":10,\"seeders\":2}");
}

#[test]
fn it_should_serialize_pretty_json_when_pretty_is_true() {
let response = SampleResponse {
transaction_id: 10,
seeders: 2,
};

let json = response.to_json_string(true).expect("it should serialize pretty JSON");

assert!(json.contains('\n'));
assert!(json.contains(" \"transaction_id\": 10"));
assert!(json.contains(" \"seeders\": 2"));
}
}
Loading
Loading