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
33 changes: 30 additions & 3 deletions .github/workflows/testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,41 @@ jobs:
name: Run Unit Tests
run: cargo test --tests --benches --examples --workspace --all-targets --all-features

database-compatibility:
name: Database Compatibility (${{ matrix.mysql-version }})
runs-on: ubuntu-latest
needs: unit

strategy:
matrix:
mysql-version: ["8.0", "8.4"]

steps:
- id: checkout
name: Checkout Repository
uses: actions/checkout@v6

- id: setup
name: Setup Toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- id: cache
name: Enable Job Cache
uses: Swatinem/rust-cache@v2

- id: database
name: Run MySQL Database Tests
run: TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test --package bittorrent-tracker-core
name: Run Database Compatibility Test
env:
TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST: "true"
TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG: ${{ matrix.mysql-version }}
run: cargo test -p bittorrent-tracker-core --features db-compatibility-tests run_mysql_driver_tests -- --nocapture

e2e:
name: E2E
runs-on: ubuntu-latest
needs: unit
needs: database-compatibility

strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion docs/issues/1525-overhaul-persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ You can then browse or search it while working in the main repository.

### 1) Add DB compatibility matrix

- Spec file: `docs/issues/1525-01-persistence-test-coverage.md`
- Spec file: `docs/issues/1703-1525-01-persistence-test-coverage.md`
- Outcome: compatibility matrix exercises SQLite and multiple MySQL versions; PostgreSQL slot
reserved for subissue 8

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Subissue Draft for #1525-01: Add DB Compatibility Matrix
# Subissue #1703 (Draft for #1525-01): Add DB Compatibility Matrix

- Issue: https://github.com/torrust/torrust-tracker/issues/1703

## Goal

Expand Down Expand Up @@ -42,7 +44,7 @@ The implementation must follow these quality rules for all new and modified test

The PR #1695 review branch includes a QA script that defines the expected behavior:

- `run-db-compatibility-matrix.sh`:
- `database-compatibility` job in `.github/workflows/testing.yaml`:
executes a compatibility matrix across SQLite, multiple MySQL versions, and multiple PostgreSQL
versions.

Expand Down Expand Up @@ -86,51 +88,44 @@ Steps:
- PostgreSQL (reserved for subissue #1525-08): `TORRUST_TRACKER_CORE_POSTGRES_DRIVER_IMAGE_TAG`

When `TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG` is not set, the test falls back to the
current hardcoded default (e.g. `8.0`), preserving existing behavior. The matrix script sets
current hardcoded default (e.g. `8.0`), preserving existing behavior. The CI matrix job sets
this variable explicitly for each version in the loop, so unset means "run as today" and the
matrix just expands that into multiple combinations.

- Add `contrib/dev-tools/qa/run-db-compatibility-matrix.sh` modeled after the PR prototype:
- `set -euo pipefail`
- define default version sets from env vars:
- `MYSQL_VERSIONS` defaulting to at least `8.0 8.4`
- `POSTGRES_VERSIONS` reserved for subissue #1525-08
- run pre-checks once (`cargo check --workspace --all-targets`)
- run protocol/configuration tests once
- run SQLite driver tests once
- loop MySQL versions: `docker pull mysql:<version>`, then run MySQL driver tests with
`TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=1` and
`TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG=<version>`
- print a clear heading for each backend/version before executing tests
- fail fast on first failure with the failing backend/version visible in logs
- keep script complexity intentionally low; avoid re-implementing test logic already in test
functions
- Replace the current single MySQL `database` step in `.github/workflows/testing.yaml` with
execution of the new script.
- Add a dedicated `database-compatibility` workflow job (between unit and e2e) with matrix values for MySQL versions:
- include matrix values for at least `8.0` and `8.4`
- run `cargo test -p bittorrent-tracker-core --features db-compatibility-tests run_mysql_driver_tests -- --nocapture`
- set `TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true`
- set `TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG=<version>`
- keep the test logic in Rust; use workflow matrix for version fan-out
- Replace the current single MySQL `database` step in `.github/workflows/testing.yaml` with a
dedicated `database-compatibility` job.

Acceptance criteria:

- [ ] DB image version injection is supported via `TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG`
(and a reserved `POSTGRES` equivalent for subissue #1525-08).
- [ ] `contrib/dev-tools/qa/run-db-compatibility-matrix.sh` exists and runs successfully.
- [ ] The script exercises SQLite and at least two MySQL versions by default.
- [ ] `database-compatibility` workflow job runs successfully for each configured MySQL version.
- [ ] The workflow matrix exercises at least two MySQL versions by default.
- [ ] Failures identify the backend/version combination that broke.
- [ ] The `database` job step in `.github/workflows/testing.yaml` runs the matrix script instead
of a single-version MySQL command.
- [ ] The script structure allows PostgreSQL to be added in subissue #1525-08 without a redesign.
- [ ] The dedicated `database-compatibility` job in `.github/workflows/testing.yaml` replaces the
old single-version MySQL command.
- [ ] The workflow matrix structure allows PostgreSQL to be added in subissue #1525-08 without a
redesign.
- [ ] Tests do not hard-code host ports; `testcontainers` assigns random ports automatically.
- [ ] All containers started by tests are removed unconditionally on test completion or failure.

### 2) Document the workflow

Steps:

- Document the local invocation command for the matrix script.
- Document that the CI `database` step runs the same script.
- Document the local invocation command for the compatibility test using explicit feature + env
vars.
- Document that CI runs the same test through the `database-compatibility` workflow job matrix.

Acceptance criteria:

- [ ] The matrix script is documented and runnable without ad hoc manual steps.
- [ ] The compatibility test command is documented and runnable without ad hoc manual steps.

## Out of Scope

Expand All @@ -143,13 +138,13 @@ Acceptance criteria:

- [ ] `cargo test --workspace --all-targets` passes.
- [ ] `linter all` exits with code `0`.
- [ ] The matrix script has been executed successfully in a clean environment; a passing run log
is included in the PR description.
- [ ] The `database-compatibility` workflow job has been executed successfully in a clean
environment; a passing run log is included in the PR description.

## References

- EPIC: #1525
- Reference PR: #1695
- Reference implementation branch: `josecelano:pr-1684-review` — see EPIC for checkout
instructions (`docs/issues/1525-overhaul-persistence.md`)
- Reference script: `contrib/dev-tools/qa/run-db-compatibility-matrix.sh`
- Reference job: `.github/workflows/testing.yaml` `database-compatibility`
20 changes: 20 additions & 0 deletions packages/http-protocol/src/v1/responses/scrape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,25 @@ mod tests {
String::from_utf8(expected_bytes.to_vec()).unwrap()
);
}

#[test]
fn should_encode_large_download_counts_as_i64() {
let info_hash = InfoHash::from_bytes(&[0x69; 20]);
let mut scrape_data = ScrapeData::empty();
scrape_data.add_file(
&info_hash,
SwarmMetadata {
complete: 1,
downloaded: u32::MAX,
incomplete: 3,
},
);

let response = Bencoded::from(scrape_data);
let bytes = response.body();
let body = String::from_utf8(bytes).unwrap();

assert!(body.contains(&format!("downloadedi{}e", i64::from(u32::MAX))));
}
Comment thread
josecelano marked this conversation as resolved.
}
}
4 changes: 4 additions & 0 deletions packages/tracker-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ repository.workspace = true
rust-version.workspace = true
version.workspace = true

[features]
default = [ ]
db-compatibility-tests = [ ]

[dependencies]
aquatic_udp_protocol = "0"
bittorrent-primitives = "0.1.0"
Expand Down
11 changes: 8 additions & 3 deletions packages/tracker-core/src/databases/driver/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ impl Database for Mysql {
}
}

#[cfg(test)]
#[cfg(all(test, feature = "db-compatibility-tests"))]
mod tests {
use std::sync::Arc;

Expand All @@ -355,7 +355,8 @@ mod tests {

Test for this driver are executed with:

`TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test`
`TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true \
cargo test -p bittorrent-tracker-core --features db-compatibility-tests run_mysql_driver_tests`

The `Database` trait is very simple and we only have one driver that needs
a container. In the future we might want to use different approaches like:
Expand All @@ -379,7 +380,9 @@ mod tests {

impl StoppedMysqlContainer {
async fn run(self, config: &MysqlConfiguration) -> Result<RunningMysqlContainer, Box<dyn std::error::Error + 'static>> {
let container = GenericImage::new("mysql", "8.0")
let image_tag = std::env::var("TORRUST_TRACKER_CORE_MYSQL_DRIVER_IMAGE_TAG").unwrap_or_else(|_| "8.0".to_string());

let container = GenericImage::new("mysql", image_tag.as_str())
.with_exposed_port(config.internal_port.tcp())
// todo: this does not work
//.with_wait_for(WaitFor::message_on_stdout("ready for connections"))
Expand Down Expand Up @@ -454,6 +457,8 @@ mod tests {
driver
}

// This test is invoked by `.github/workflows/testing.yaml` in the
// `database-compatibility` job to validate supported MySQL versions.
#[tokio::test]
async fn run_mysql_driver_tests() -> Result<(), Box<dyn std::error::Error + 'static>> {
if std::env::var("TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST").is_err() {
Expand Down
24 changes: 17 additions & 7 deletions packages/udp-tracker-server/src/handlers/scrape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,22 @@ pub async fn handle_scrape(
Ok(build_response(request, &scrape_data))
}

fn udp_counter_from_u32(value: u32) -> i32 {
// Temporary saturation guard for UDP i32 counters. Proper type alignment across Rust and DB layers
// will be addressed in docs/issues/1525-07-align-rust-and-db-types.md.
i32::try_from(value).unwrap_or(i32::MAX)
}

fn build_response(request: &ScrapeRequest, scrape_data: &ScrapeData) -> Response {
let mut torrent_stats: Vec<TorrentScrapeStatistics> = Vec::new();

for file in &scrape_data.files {
let swarm_metadata = file.1;

#[allow(clippy::cast_possible_truncation)]
let scrape_entry = {
TorrentScrapeStatistics {
seeders: NumberOfPeers(I32::new(i64::from(swarm_metadata.complete) as i32)),
completed: NumberOfDownloads(I32::new(i64::from(swarm_metadata.downloaded) as i32)),
leechers: NumberOfPeers(I32::new(i64::from(swarm_metadata.incomplete) as i32)),
}
let scrape_entry = TorrentScrapeStatistics {
seeders: NumberOfPeers(I32::new(udp_counter_from_u32(swarm_metadata.complete))),
completed: NumberOfDownloads(I32::new(udp_counter_from_u32(swarm_metadata.downloaded))),
leechers: NumberOfPeers(I32::new(udp_counter_from_u32(swarm_metadata.incomplete))),
};

torrent_stats.push(scrape_entry);
Expand Down Expand Up @@ -458,4 +461,11 @@ mod tests {
}
}
}

#[test]
fn should_saturate_large_download_counts_for_udp_protocol() {
assert_eq!(super::udp_counter_from_u32(u32::MAX), i32::MAX);
assert_eq!(super::udp_counter_from_u32((i32::MAX as u32) + 1), i32::MAX);
assert_eq!(super::udp_counter_from_u32(42), 42);
}
}