refactor(download): 纯搬移为 M289 腾出行数余量(config 单测 / engine 只读查询面)

两个文件都是 400 行零余量的硬线,本 mission 要加超时字段与失速诊断,先纯搬移:

* `config.rs` 的 `#[cfg(test)] mod tests`(174 行)→ `config/tests.rs`,
  原文件用 `#[cfg(test)] #[path = "config/tests.rs"] mod tests;` 声明;
* `engine.rs` 的七个只读 getter(`config` / `concurrency_probe` / `client` /
  `region` / `mirrors` / `health` / `cache`,44 行)→ `engine/inspect.rs`。

逐字节未改:搬移块去缩进后与 HEAD 原文 `diff` 均为 exit 0。

行为零变化(同一批测试仍全绿);新文件都在 `crates/core/src/download/**` 内,
外部路径(`download::config::EngineConfig` 等)不变。
This commit is contained in:
AskaEth committed 2026-11-12 23:24:18 +08:00
1 parent 8b48c494cd
commit 61c8a4eff9
4 files changed
+237 -219

No files matched your search

+2 -175
View File
@@ -223,178 +223,5 @@ pub fn publish(cache_root: &Path, next: DownloadTuning) -> bool {
} }
#[cfg(test)] #[cfg(test)]
mod tests { #[path = "config/tests.rs"]
use super::*; mod tests;
use crate::setting::keys::{launcher_schema, launcher_version};
/// **守门③**:schema 的默认值 / 区间 / 键名与 `EngineConfig::default()` / 调优默认值取同一组常量。
/// 变异证明(只改实现侧):`EngineConfig::default()` 的并发写回 64、schema 默认值写成 16、
/// `MAX_CONCURRENCY` 改了而 schema 仍写 64 ⇒ 各自红。
#[test]
fn the_schema_contract_is_single_sourced() {
let schema = launcher_schema();
assert_eq!(schema.version(), launcher_version());
let cfg = EngineConfig::default();
let tune = DownloadTuning::default();
// 并发 1~64(0 张许可没意义);尝试次数 **0**~10(P2-1:0 = 只试一次,旧配置里真有)。
let contract: [(&str, i64, i64, usize); 2] = [
(
KEY_CONCURRENCY,
i64::try_from(DEFAULT_MAX_CONCURRENCY).expect("装得下"),
1,
MAX_CONCURRENCY,
),
(
KEY_RETRY_LIMIT,
i64::from(DEFAULT_MAX_ATTEMPTS_PER_SOURCE),
0,
usize::try_from(MAX_ATTEMPTS_PER_SOURCE).expect("装得下"),
),
];
for (key, default, min, max) in contract {
let spec = schema.spec(key).expect("键已登记");
assert_eq!(spec.default, serde_json::json!(default), "{key}");
assert_eq!(
spec.kind,
crate::setting::spec::ValueKind::Int {
min,
max: i64::try_from(max).expect("装得下"),
},
"{key}"
);
}
assert_eq!(cfg.max_concurrency, DEFAULT_MAX_CONCURRENCY);
assert_eq!(tune.max_concurrency, DEFAULT_MAX_CONCURRENCY);
assert_eq!(cfg.max_attempts_per_source, DEFAULT_MAX_ATTEMPTS_PER_SOURCE);
assert_eq!(
tune.max_attempts_per_source,
DEFAULT_MAX_ATTEMPTS_PER_SOURCE
);
}
/// `SettingSpec.doc` 是 `&'static str`(`concat!` 插不进常量),描述里的数字只能手写——这条钉住它,
/// 改常量不改文案就红(`catalog.rs` 会把这些 doc 当 JSON schema 的 description 发出去)。
#[test]
fn the_key_docs_quote_the_real_limits() {
let schema = launcher_schema();
let doc = |key: &str| schema.spec(key).expect("键已登记").doc;
assert!(doc(KEY_CONCURRENCY).contains(&format!("1~{MAX_CONCURRENCY}")));
assert!(doc(KEY_CONCURRENCY).contains(&format!("出厂 {DEFAULT_MAX_CONCURRENCY}")));
assert!(doc(KEY_RETRY_LIMIT).contains(&format!("出厂 {DEFAULT_MAX_ATTEMPTS_PER_SOURCE}")));
}
/// 未填 / 越界回落默认(不阻断下载);**`retry_limit: 0` 是合法值**,语义 = 只试一次。
///
/// 变异证明:把并发的区间过滤删掉 ⇒ `0` / `9999` 两条红;把尝试次数的 `.max(1)` 删掉 ⇒
/// `0 → 1` 那条红(引擎会拿到 0 次尝试);把 `0` 判成越界 ⇒ 同样红。
#[test]
fn missing_or_illegal_values_fall_back_to_the_defaults() {
assert_eq!(
DownloadTuning::from_settings(None, None, None),
DownloadTuning::default()
);
let just_over = i64::try_from(MAX_CONCURRENCY).expect("上限装得下") + 1;
for bad in [0_i64, -1, 9_999, just_over] {
let tune = DownloadTuning::from_settings(Some(bad), None, None);
assert_eq!(tune.max_concurrency, DEFAULT_MAX_CONCURRENCY, "{bad}");
}
// 0 与 1 都 = 只试一次:旧配置里的 0 不许回落成出厂 3(那正是「界面 0、引擎 3」)。
for zero_or_one in [0_i64, 1] {
let tune = DownloadTuning::from_settings(None, Some(zero_or_one), None);
assert_eq!(tune.max_attempts_per_source, 1, "{zero_or_one}");
}
for bad in [-1_i64, 9_999] {
let tune = DownloadTuning::from_settings(None, Some(bad), None);
assert_eq!(
tune.max_attempts_per_source,
DEFAULT_MAX_ATTEMPTS_PER_SOURCE
);
}
let tune = DownloadTuning::from_settings(Some(2), Some(2), None);
assert_eq!(tune.max_concurrency, 2);
assert_eq!(tune.max_attempts_per_source, 2);
}
/// 三态:**空串 ≠ 关**(这条是「设置项说假话」的高发点,M197 的键语义同源)。
#[test]
fn empty_gh_proxy_means_follow_builtin_not_off() {
assert_eq!(gh_proxy_override(""), GhProxyOverride::Builtin);
assert_eq!(gh_proxy_override(" "), GhProxyOverride::Builtin);
for off in [
"off", "OFF", " none ", "direct", "disable", "disabled", "no",
] {
assert_eq!(gh_proxy_override(off), GhProxyOverride::Off, "{off}");
}
assert_eq!(
gh_proxy_override(" https://gh.example/ "),
GhProxyOverride::Prefix("https://gh.example/".to_string())
);
// 未填(键不存在)也是 Builtin,不是 Off。
assert_eq!(
DownloadTuning::from_settings(None, None, None).gh_proxy,
GhProxyOverride::Builtin
);
}
/// `apply_gh_proxy` 的三档真的改到档案上、并且**改到候选链上**(`expand()` 读的就是档案里那个
/// 前缀;自定义前缀那一档的链断言在 `shared.rs`)。变异证明:`Off` 分支改成原样返回 ⇒ `off` 红。
#[test]
fn gh_proxy_override_reaches_the_profile_and_the_chain() {
use crate::download::mirrors::{MirrorTable, SOURCE_GH_PROXY};
use crate::download::types::ResourceKind;
let url = "https://github.com/o/r/releases/download/v1/a.jar";
let table = MirrorTable::builtin();
let sources = |profile: &RegionProfile| -> Vec<String> {
table
.expand(url, ResourceKind::Other, profile)
.into_iter()
.map(|candidate| candidate.source)
.collect()
};
let mirror_first = RegionProfile::mirror_first(0);
let kept = DownloadTuning::default().apply_gh_proxy(mirror_first.clone());
assert_eq!(kept.gh_proxy, mirror_first.gh_proxy, "Builtin 不该动档案");
assert!(
sources(&kept).contains(&SOURCE_GH_PROXY.to_string()),
"出厂套前缀"
);
let off = DownloadTuning::from_settings(None, None, Some("off"))
.apply_gh_proxy(mirror_first.clone());
assert!(
!sources(&off).contains(&SOURCE_GH_PROXY.to_string()),
"关了就没候选"
);
let prefix = DownloadTuning::from_settings(None, None, Some("https://gh.example/"))
.apply_gh_proxy(mirror_first);
assert_eq!(prefix.gh_proxy.as_deref(), Some("https://gh.example/"));
assert!(prefix.gh_proxy_enabled(), "前缀生效");
}
/// 每个数据根的调优互不串味(一个根改成 2 不该改另一个根的引擎)。
/// 变异证明:把 `wanted`/`publish` 的键改回单一进程级槽位 ⇒ 第二个根读到 2,红。
#[test]
fn tuning_is_per_cache_root_and_no_change_is_reported_as_such() {
let a = PathBuf::from("/tmp/ull-tuning-a");
let b = PathBuf::from("/tmp/ull-tuning-b");
assert_eq!(wanted(&a), DownloadTuning::default());
let changed = DownloadTuning::from_settings(Some(2), None, None);
assert!(publish(&a, changed.clone()), "真变了要报变更");
assert_eq!(wanted(&a), changed);
assert!(!publish(&a, changed.clone()), "同值不该报变更");
assert_eq!(wanted(&b), DownloadTuning::default(), "别的根不受影响");
assert!(
publish(&a, DownloadTuning::default()),
"恢复默认也是一次变更"
);
assert_eq!(wanted(&a), DownloadTuning::default());
}
}
+175
View File
@@ -0,0 +1,175 @@
//! `download::config` 的单测:**从 `config.rs` 拆出只为守 400 行硬线**(M289 加超时字段前先纯搬移,逐字节未改)。
use super::*;
use crate::setting::keys::{launcher_schema, launcher_version};
/// **守门③**:schema 的默认值 / 区间 / 键名与 `EngineConfig::default()` / 调优默认值取同一组常量。
/// 变异证明(只改实现侧):`EngineConfig::default()` 的并发写回 64、schema 默认值写成 16、
/// `MAX_CONCURRENCY` 改了而 schema 仍写 64 ⇒ 各自红。
#[test]
fn the_schema_contract_is_single_sourced() {
let schema = launcher_schema();
assert_eq!(schema.version(), launcher_version());
let cfg = EngineConfig::default();
let tune = DownloadTuning::default();
// 并发 1~64(0 张许可没意义);尝试次数 **0**~10(P2-1:0 = 只试一次,旧配置里真有)。
let contract: [(&str, i64, i64, usize); 2] = [
(
KEY_CONCURRENCY,
i64::try_from(DEFAULT_MAX_CONCURRENCY).expect("装得下"),
1,
MAX_CONCURRENCY,
),
(
KEY_RETRY_LIMIT,
i64::from(DEFAULT_MAX_ATTEMPTS_PER_SOURCE),
0,
usize::try_from(MAX_ATTEMPTS_PER_SOURCE).expect("装得下"),
),
];
for (key, default, min, max) in contract {
let spec = schema.spec(key).expect("键已登记");
assert_eq!(spec.default, serde_json::json!(default), "{key}");
assert_eq!(
spec.kind,
crate::setting::spec::ValueKind::Int {
min,
max: i64::try_from(max).expect("装得下"),
},
"{key}"
);
}
assert_eq!(cfg.max_concurrency, DEFAULT_MAX_CONCURRENCY);
assert_eq!(tune.max_concurrency, DEFAULT_MAX_CONCURRENCY);
assert_eq!(cfg.max_attempts_per_source, DEFAULT_MAX_ATTEMPTS_PER_SOURCE);
assert_eq!(
tune.max_attempts_per_source,
DEFAULT_MAX_ATTEMPTS_PER_SOURCE
);
}
/// `SettingSpec.doc` 是 `&'static str`(`concat!` 插不进常量),描述里的数字只能手写——这条钉住它,
/// 改常量不改文案就红(`catalog.rs` 会把这些 doc 当 JSON schema 的 description 发出去)。
#[test]
fn the_key_docs_quote_the_real_limits() {
let schema = launcher_schema();
let doc = |key: &str| schema.spec(key).expect("键已登记").doc;
assert!(doc(KEY_CONCURRENCY).contains(&format!("1~{MAX_CONCURRENCY}")));
assert!(doc(KEY_CONCURRENCY).contains(&format!("出厂 {DEFAULT_MAX_CONCURRENCY}")));
assert!(doc(KEY_RETRY_LIMIT).contains(&format!("出厂 {DEFAULT_MAX_ATTEMPTS_PER_SOURCE}")));
}
/// 未填 / 越界回落默认(不阻断下载);**`retry_limit: 0` 是合法值**,语义 = 只试一次。
///
/// 变异证明:把并发的区间过滤删掉 ⇒ `0` / `9999` 两条红;把尝试次数的 `.max(1)` 删掉 ⇒
/// `0 → 1` 那条红(引擎会拿到 0 次尝试);把 `0` 判成越界 ⇒ 同样红。
#[test]
fn missing_or_illegal_values_fall_back_to_the_defaults() {
assert_eq!(
DownloadTuning::from_settings(None, None, None),
DownloadTuning::default()
);
let just_over = i64::try_from(MAX_CONCURRENCY).expect("上限装得下") + 1;
for bad in [0_i64, -1, 9_999, just_over] {
let tune = DownloadTuning::from_settings(Some(bad), None, None);
assert_eq!(tune.max_concurrency, DEFAULT_MAX_CONCURRENCY, "{bad}");
}
// 0 与 1 都 = 只试一次:旧配置里的 0 不许回落成出厂 3(那正是「界面 0、引擎 3」)。
for zero_or_one in [0_i64, 1] {
let tune = DownloadTuning::from_settings(None, Some(zero_or_one), None);
assert_eq!(tune.max_attempts_per_source, 1, "{zero_or_one}");
}
for bad in [-1_i64, 9_999] {
let tune = DownloadTuning::from_settings(None, Some(bad), None);
assert_eq!(
tune.max_attempts_per_source,
DEFAULT_MAX_ATTEMPTS_PER_SOURCE
);
}
let tune = DownloadTuning::from_settings(Some(2), Some(2), None);
assert_eq!(tune.max_concurrency, 2);
assert_eq!(tune.max_attempts_per_source, 2);
}
/// 三态:**空串 ≠ 关**(这条是「设置项说假话」的高发点,M197 的键语义同源)。
#[test]
fn empty_gh_proxy_means_follow_builtin_not_off() {
assert_eq!(gh_proxy_override(""), GhProxyOverride::Builtin);
assert_eq!(gh_proxy_override(" "), GhProxyOverride::Builtin);
for off in [
"off", "OFF", " none ", "direct", "disable", "disabled", "no",
] {
assert_eq!(gh_proxy_override(off), GhProxyOverride::Off, "{off}");
}
assert_eq!(
gh_proxy_override(" https://gh.example/ "),
GhProxyOverride::Prefix("https://gh.example/".to_string())
);
// 未填(键不存在)也是 Builtin,不是 Off。
assert_eq!(
DownloadTuning::from_settings(None, None, None).gh_proxy,
GhProxyOverride::Builtin
);
}
/// `apply_gh_proxy` 的三档真的改到档案上、并且**改到候选链上**(`expand()` 读的就是档案里那个
/// 前缀;自定义前缀那一档的链断言在 `shared.rs`)。变异证明:`Off` 分支改成原样返回 ⇒ `off` 红。
#[test]
fn gh_proxy_override_reaches_the_profile_and_the_chain() {
use crate::download::mirrors::{MirrorTable, SOURCE_GH_PROXY};
use crate::download::types::ResourceKind;
let url = "https://github.com/o/r/releases/download/v1/a.jar";
let table = MirrorTable::builtin();
let sources = |profile: &RegionProfile| -> Vec<String> {
table
.expand(url, ResourceKind::Other, profile)
.into_iter()
.map(|candidate| candidate.source)
.collect()
};
let mirror_first = RegionProfile::mirror_first(0);
let kept = DownloadTuning::default().apply_gh_proxy(mirror_first.clone());
assert_eq!(kept.gh_proxy, mirror_first.gh_proxy, "Builtin 不该动档案");
assert!(
sources(&kept).contains(&SOURCE_GH_PROXY.to_string()),
"出厂套前缀"
);
let off = DownloadTuning::from_settings(None, None, Some("off"))
.apply_gh_proxy(mirror_first.clone());
assert!(
!sources(&off).contains(&SOURCE_GH_PROXY.to_string()),
"关了就没候选"
);
let prefix = DownloadTuning::from_settings(None, None, Some("https://gh.example/"))
.apply_gh_proxy(mirror_first);
assert_eq!(prefix.gh_proxy.as_deref(), Some("https://gh.example/"));
assert!(prefix.gh_proxy_enabled(), "前缀生效");
}
/// 每个数据根的调优互不串味(一个根改成 2 不该改另一个根的引擎)。
/// 变异证明:把 `wanted`/`publish` 的键改回单一进程级槽位 ⇒ 第二个根读到 2,红。
#[test]
fn tuning_is_per_cache_root_and_no_change_is_reported_as_such() {
let a = PathBuf::from("/tmp/ull-tuning-a");
let b = PathBuf::from("/tmp/ull-tuning-b");
assert_eq!(wanted(&a), DownloadTuning::default());
let changed = DownloadTuning::from_settings(Some(2), None, None);
assert!(publish(&a, changed.clone()), "真变了要报变更");
assert_eq!(wanted(&a), changed);
assert!(!publish(&a, changed.clone()), "同值不该报变更");
assert_eq!(wanted(&b), DownloadTuning::default(), "别的根不受影响");
assert!(
publish(&a, DownloadTuning::default()),
"恢复默认也是一次变更"
);
assert_eq!(wanted(&a), DownloadTuning::default());
}
+2 -44
View File
@@ -35,6 +35,8 @@ use super::types::{
SOURCE_OFFICIAL, SOURCE_OFFICIAL,
}; };
mod inspect;
/// 一次成功尝试的产物描述。 /// 一次成功尝试的产物描述。
pub(crate) struct AttemptOk { pub(crate) struct AttemptOk {
/// 文件总字节数。 /// 文件总字节数。
@@ -94,50 +96,6 @@ impl DownloadEngine {
self self
} }
/// 配置。
#[must_use]
pub fn config(&self) -> &EngineConfig {
&self.cfg
}
/// **真实并发闸门**此刻的许可数(探针,M193):读 [`Self::acquire`] 会去抢的那张
/// `Semaphore`,**不是** `config().max_concurrency`。有下载在飞时偏小,空闲等于容量。
#[must_use]
pub fn concurrency_probe(&self) -> usize {
self.sem.available_permits()
}
/// HTTP 客户端(同一条连接池与超时配置):给「一锤子查询」用(如 Adoptium 版本查询),
/// 没必要为了一个 GET 再造一个 client。
#[must_use]
pub fn client(&self) -> &reqwest::Client {
&self.http
}
/// 当前地区档案(设置页展示)。
#[must_use]
pub fn region(&self) -> &RegionProfile {
&self.region
}
/// 镜像表(`game::java` 的 Oracle / MS / Zulu 分发表在这里查)。
#[must_use]
pub fn mirrors(&self) -> &MirrorTable {
&self.mirrors
}
/// 健康度注册表(诊断页)。
#[must_use]
pub fn health(&self) -> &HealthRegistry {
&self.health
}
/// 内容寻址缓存。
#[must_use]
pub fn cache(&self) -> &ContentCache {
&self.cache
}
/// 候选规划:镜像展开 → 官方位置保持不变 → 其余按健康度排序。 /// 候选规划:镜像展开 → 官方位置保持不变 → 其余按健康度排序。
#[must_use] #[must_use]
pub fn plan(&self, req: &DownloadRequest, now_ms: u64) -> Vec<Candidate> { pub fn plan(&self, req: &DownloadRequest, now_ms: u64) -> Vec<Candidate> {
@@ -0,0 +1,58 @@
//! 引擎的只读查询面:**从 `engine.rs` 拆出只为守 400 行硬线**(M289 加失速诊断前先纯搬移,逐字节未改)。
//!
//! 这里只有「读」——配置、并发闸门探针、HTTP 客户端、地区档案、镜像表、健康度、内容缓存。
//! 编排(候选循环、退避、落盘)仍在 [`super`]。
use super::super::cache::ContentCache;
use super::super::config::EngineConfig;
use super::super::health::HealthRegistry;
use super::super::mirrors::MirrorTable;
use super::super::region::RegionProfile;
use super::DownloadEngine;
impl DownloadEngine {
/// 配置。
#[must_use]
pub fn config(&self) -> &EngineConfig {
&self.cfg
}
/// **真实并发闸门**此刻的许可数(探针,M193):读 [`Self::acquire`] 会去抢的那张
/// `Semaphore`,**不是** `config().max_concurrency`。有下载在飞时偏小,空闲等于容量。
#[must_use]
pub fn concurrency_probe(&self) -> usize {
self.sem.available_permits()
}
/// HTTP 客户端(同一条连接池与超时配置):给「一锤子查询」用(如 Adoptium 版本查询),
/// 没必要为了一个 GET 再造一个 client。
#[must_use]
pub fn client(&self) -> &reqwest::Client {
&self.http
}
/// 当前地区档案(设置页展示)。
#[must_use]
pub fn region(&self) -> &RegionProfile {
&self.region
}
/// 镜像表(`game::java` 的 Oracle / MS / Zulu 分发表在这里查)。
#[must_use]
pub fn mirrors(&self) -> &MirrorTable {
&self.mirrors
}
/// 健康度注册表(诊断页)。
#[must_use]
pub fn health(&self) -> &HealthRegistry {
&self.health
}
/// 内容寻址缓存。
#[must_use]
pub fn cache(&self) -> &ContentCache {
&self.cache
}
}