mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 04:35:16 +08:00
fix(daemon): 区分当前下载与历史失败状态
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ Rust 侧官方日服资源链路已经从实验验证推进到正式入口:
|
|||||||
8. 远端 snapshot 未变化但输出目录为空时,会按首次运行执行全量拉取;官方 seed `.hash` 校验失败时会清理对应 manifest 条目,避免失败产物被后续本地 audit 误判为可复用。
|
8. 远端 snapshot 未变化但输出目录为空时,会按首次运行执行全量拉取;官方 seed `.hash` 校验失败时会清理对应 manifest 条目,避免失败产物被后续本地 audit 误判为可复用。
|
||||||
9. 默认资源目录是 `./bat-resources`,默认后台状态目录是 `/tmp/bat-pid`;资源目录是发布根目录,包含 `current` symlink、`versions/<id>` 和 `.staging/<id>`,非 dry-run 会先写 staging,校验完成后发布 versioned 目录并原子切换 `current`;后台状态目录包含 `bat.sock`、`bat.pid`、`bat-status.json`、`bat-daemon.log`、`bat-events.jsonl` 和短生命周期 `bat-control.lock`;非 dry-run 使用 `--output/.official-sync.lock` 防止并发写同一资源目录,live daemon 会阻止前台写命令直接修改它正在管理的同一目录。
|
9. 默认资源目录是 `./bat-resources`,默认后台状态目录是 `/tmp/bat-pid`;资源目录是发布根目录,包含 `current` symlink、`versions/<id>` 和 `.staging/<id>`,非 dry-run 会先写 staging,校验完成后发布 versioned 目录并原子切换 `current`;后台状态目录包含 `bat.sock`、`bat.pid`、`bat-status.json`、`bat-daemon.log`、`bat-events.jsonl` 和短生命周期 `bat-control.lock`;非 dry-run 使用 `--output/.official-sync.lock` 防止并发写同一资源目录,live daemon 会阻止前台写命令直接修改它正在管理的同一目录。
|
||||||
10. 官方同步会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
10. 官方同步会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
||||||
11. `<output>/official-version-state.json` 会明确保存当前已完成版本、正在拉取版本、上一个可用版本和失败版本;重复失败按 app version、bundle version、Addressables root 和错误原因合并只保留最新一条,同一版本后续发布成功时会清理对应失败记录;`bat status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近失败版本和原因、结构化日志路径和轮转日志路径,人类可读输出不会把完整版本状态 JSON 内联打印。
|
11. `<output>/official-version-state.json` 会明确保存当前已完成版本、正在拉取版本、上一个可用版本和失败版本;同一 app version、bundle version 和 Addressables root 的失败只保留最新一条,同一版本开始重新拉取或后续发布成功时会清理对应失败记录;`bat status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近历史失败版本和原因、结构化日志路径和轮转日志路径,人类可读输出不会把完整版本状态 JSON 内联打印。
|
||||||
12. 资源导入链路已支持 CAS + `ResourceRepository` 索引写入,AssetBundle 导入会记录 UnityFS 摘要,TextAsset/Table/Media 会按类型分类;当前/上一个/结构变化 catalog、403/404、hash mismatch 均有离线回归 fixture。
|
12. 资源导入链路已支持 CAS + `ResourceRepository` 索引写入,AssetBundle 导入会记录 UnityFS 摘要,TextAsset/Table/Media 会按类型分类;当前/上一个/结构变化 catalog、403/404、hash mismatch 均有离线回归 fixture。
|
||||||
|
|
||||||
仍需明确:这不是完整产品完成。Go CLI 最小入口、完整 AssetBundle 解析、Patch、翻译系统、API Server 和 Web 仍是后续工作;真实官方网络全量拉取 smoke 已固化为可重复脚本和 runbook(G-018 已关闭),当前正在进行长期运行测试,运行报告将在后续提供;真实大文件产物与运行报告默认保存在 `/tmp` 隔离目录,不纳入 Git。
|
仍需明确:这不是完整产品完成。Go CLI 最小入口、完整 AssetBundle 解析、Patch、翻译系统、API Server 和 Web 仍是后续工作;真实官方网络全量拉取 smoke 已固化为可重复脚本和 runbook(G-018 已关闭),当前正在进行长期运行测试,运行报告将在后续提供;真实大文件产物与运行报告默认保存在 `/tmp` 隔离目录,不纳入 Git。
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ Linux 生产路径:
|
|||||||
- `OfficialUpdateService` 能持久化 v2 snapshot,并在远端 marker 内容变化时触发下载决策
|
- `OfficialUpdateService` 能持久化 v2 snapshot,并在远端 marker 内容变化时触发下载决策
|
||||||
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress log,stdout 默认输出人类可读摘要;progress log 覆盖总体下载进度、单文件开始/完成状态、下载中断失败分类和校验结果摘要;支持 `--json` 输出稳定 JSON,支持 `--no-progress` 关闭进度日志,支持 `--no-banner` 只关闭横幅,支持 `--watch --interval 1h --error-retry 60s` 常驻运行,支持 `--daemon` Unix socket JSON-RPC 控制、`status`、`stop`、`restart`、`reload`、`logs`、`refresh --force`、`verify`、`repair`、`doctor`、`clean-stable`,非 dry-run 使用 `.official-sync.lock` 防止并发写资源目录,控制命令使用 `bat-control.lock` 防止并发状态修改,资源发布使用 `.staging`、`versions` 和 `current` 原子切换,daemon 写 `bat-events.jsonl` 结构化日志并在 `status` 中暴露下载进度、失败类型、HTTP 状态和调度状态
|
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress log,stdout 默认输出人类可读摘要;progress log 覆盖总体下载进度、单文件开始/完成状态、下载中断失败分类和校验结果摘要;支持 `--json` 输出稳定 JSON,支持 `--no-progress` 关闭进度日志,支持 `--no-banner` 只关闭横幅,支持 `--watch --interval 1h --error-retry 60s` 常驻运行,支持 `--daemon` Unix socket JSON-RPC 控制、`status`、`stop`、`restart`、`reload`、`logs`、`refresh --force`、`verify`、`repair`、`doctor`、`clean-stable`,非 dry-run 使用 `.official-sync.lock` 防止并发写资源目录,控制命令使用 `bat-control.lock` 防止并发状态修改,资源发布使用 `.staging`、`versions` 和 `current` 原子切换,daemon 写 `bat-events.jsonl` 结构化日志并在 `status` 中暴露下载进度、失败类型、HTTP 状态和调度状态
|
||||||
- curl 失败分类和重试策略已覆盖 404 不重试、5xx 重试耗尽后 quarantine、launcher primary CDN 失败后切换 official backup CDN
|
- curl 失败分类和重试策略已覆盖 404 不重试、5xx 重试耗尽后 quarantine、launcher primary CDN 失败后切换 official backup CDN
|
||||||
- `official-version-state.json` 已覆盖当前完成版本、正在拉取版本、上一个可用版本和失败版本;重复失败按 app version、bundle version、Addressables root 和错误原因合并,成功发布后清理同版本失败记录,`bat status` 会暴露版本状态摘要和最近失败原因
|
- `official-version-state.json` 已覆盖当前完成版本、正在拉取版本、上一个可用版本和失败版本;同一 app version、bundle version 和 Addressables root 的失败只保留最新一条,重新拉取或成功发布后清理同版本失败记录,`bat status` 会暴露版本状态摘要和最近历史失败原因
|
||||||
- 资源导入链路已覆盖 CAS 写入、`ResourceRepository` 索引、AssetBundle UnityFS 摘要,以及 TextAsset/Table/Media 分类
|
- 资源导入链路已覆盖 CAS 写入、`ResourceRepository` 索引、AssetBundle UnityFS 摘要,以及 TextAsset/Table/Media 分类
|
||||||
- 离线回归样本已覆盖当前 catalog、上一个版本 catalog、catalog 结构变化、403、404 和 seed hash mismatch
|
- 离线回归样本已覆盖当前 catalog、上一个版本 catalog、catalog 结构变化、403、404 和 seed hash mismatch
|
||||||
- `OfficialUpdateService` 能读写 `official-bootstrap-cache.json`,并支持默认开启的 `audit_local` / `repair` CLI 行为
|
- `OfficialUpdateService` 能读写 `official-bootstrap-cache.json`,并支持默认开启的 `audit_local` / `repair` CLI 行为
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ cargo run -p bat-infrastructure --example official_pull_plan -- \
|
|||||||
- 旧 launcher 包或 `resources.assets` 下载路径使用官方 launcher CDN 配置,primary CDN 失败后会切换官方 backup CDN;资源 patch host 当前只使用 server-info 返回的官方 client-patch host,不猜测非官方镜像。
|
- 旧 launcher 包或 `resources.assets` 下载路径使用官方 launcher CDN 配置,primary CDN 失败后会切换官方 backup CDN;资源 patch host 当前只使用 server-info 返回的官方 client-patch host,不猜测非官方镜像。
|
||||||
- 远端和本地都一致:单次模式输出 `update_status=up_to_date`,watch 模式默认静默并等待下次检查。
|
- 远端和本地都一致:单次模式输出 `update_status=up_to_date`,watch 模式默认静默并等待下次检查。
|
||||||
- 有远端变化或本地 repair:生成 pull plan,下载完整官方资源到 staging,成功后更新 snapshot 并原子发布到 `current`。
|
- 有远端变化或本地 repair:生成 pull plan,下载完整官方资源到 staging,成功后更新 snapshot 并原子发布到 `current`。
|
||||||
- 非 dry-run 会维护 `<output>/official-version-state.json`:开始下载后写入 `in_progress_version`,发布成功后写入 `current_completed_version` 和 `previous_available_version`,失败或中断后写入 `failed_versions`。相同 app version、bundle version、Addressables root 和错误原因的重复失败只保留最新一条;同一版本后续发布成功时会清理对应失败记录。
|
- 非 dry-run 会维护 `<output>/official-version-state.json`:开始下载后写入 `in_progress_version`,发布成功后写入 `current_completed_version` 和 `previous_available_version`,失败或中断后写入 `failed_versions`。同一 app version、bundle version 和 Addressables root 的失败只保留最新一条;同一版本开始重新拉取或后续发布成功时会清理对应失败记录。
|
||||||
- `--dry-run`:只报告本次是否会下载,不写 snapshot;如果 cache miss,也不会写入新的 bootstrap cache。
|
- `--dry-run`:只报告本次是否会下载,不写 snapshot;如果 cache miss,也不会写入新的 bootstrap cache。
|
||||||
- `--dry-run --plan`:除更新判断外,还会解析 seed catalog 并打印完整下载 URL。
|
- `--dry-run --plan`:除更新判断外,还会解析 seed catalog 并打印完整下载 URL。
|
||||||
- 真实更新会输出 `downloaded_count`、`resumed_count`、`skipped_count`、`transferred_bytes`、`official_seed_hash_verified_count`。
|
- 真实更新会输出 `downloaded_count`、`resumed_count`、`skipped_count`、`transferred_bytes`、`official_seed_hash_verified_count`。
|
||||||
@@ -222,7 +222,7 @@ cargo run -p bat-infrastructure --bin bat -- reload
|
|||||||
cargo run -p bat-infrastructure --bin bat -- stop
|
cargo run -p bat-infrastructure --bin bat -- stop
|
||||||
```
|
```
|
||||||
|
|
||||||
`status`、`stop`、`logs`、`reload` 和默认形态的 `refresh` 会优先连接 `bat.sock`,通过 Unix socket JSON-RPC 和 live daemon 通信;socket 不可用时,`status`、`stop` 会回退到 PID/状态文件兼容路径。`status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近失败版本和原因、文本日志路径、结构化日志路径和轮转日志路径;人类输出不会把完整 `official-version-state.json` 内联打印成 JSON。控制命令会通过 `bat-control.lock` 做跨进程互斥,失效或损坏的控制锁会在下次控制命令或 `clean-stable` 时恢复。`restart` 会停止旧后台进程并按保存参数或显式参数重新启动;`reload` 在未显式传入同步参数时不会重启进程,而是唤醒或排队 watch 循环重新执行自动发现和强制刷新:空闲睡眠时立即执行,正在同步时等当前轮结束。所有命令默认输出人类可读摘要,脚本集成时加 `--json`。
|
`status`、`stop`、`logs`、`reload` 和默认形态的 `refresh` 会优先连接 `bat.sock`,通过 Unix socket JSON-RPC 和 live daemon 通信;socket 不可用时,`status`、`stop` 会回退到 PID/状态文件兼容路径。`status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近历史失败版本和原因、文本日志路径、结构化日志路径和轮转日志路径;正在重新拉取同一版本时,对应旧失败不会作为当前历史失败摘要展示;人类输出不会把完整 `official-version-state.json` 内联打印成 JSON。控制命令会通过 `bat-control.lock` 做跨进程互斥,失效或损坏的控制锁会在下次控制命令或 `clean-stable` 时恢复。`restart` 会停止旧后台进程并按保存参数或显式参数重新启动;`reload` 在未显式传入同步参数时不会重启进程,而是唤醒或排队 watch 循环重新执行自动发现和强制刷新:空闲睡眠时立即执行,正在同步时等当前轮结束。所有命令默认输出人类可读摘要,脚本集成时加 `--json`。
|
||||||
|
|
||||||
如果要把后台状态目录改到其他位置,使用 `--state-dir <目录>`:
|
如果要把后台状态目录改到其他位置,使用 `--state-dir <目录>`:
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use bat_adapters::official::yostar_jp::PatchPlatform;
|
use bat_adapters::official::yostar_jp::PatchPlatform;
|
||||||
use bat_infrastructure::{
|
use bat_infrastructure::{
|
||||||
lexical_absolute, open_append_file, read_file_no_symlink, read_version_state,
|
lexical_absolute, open_append_file, read_file_no_symlink, read_version_state,
|
||||||
validate_output_root, validate_runtime_state_dir, write_file_atomic, OfficialServerInfoSource,
|
validate_output_root, validate_runtime_state_dir, write_file_atomic,
|
||||||
OfficialUpdateConfig, OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService,
|
OfficialFailedVersionRecord, OfficialServerInfoSource, OfficialUpdateConfig,
|
||||||
OfficialUpdateStatus, OfficialVerificationSummary, OfficialVersionState, PRIVATE_FILE_MODE,
|
OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService, OfficialUpdateStatus,
|
||||||
|
OfficialVerificationSummary, OfficialVersionRecord, OfficialVersionState, PRIVATE_FILE_MODE,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
@@ -1990,14 +1991,34 @@ fn print_daemon_version_state_summary(version_state: &OfficialVersionState) {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|version| version.id.as_str()),
|
.map(|version| version.id.as_str()),
|
||||||
);
|
);
|
||||||
print_field("失败版本数", version_state.failed_versions.len());
|
let historical_failures = visible_historical_failed_versions(version_state);
|
||||||
if let Some(failed) = version_state.failed_versions.last() {
|
print_field("历史失败版本数", historical_failures.len());
|
||||||
print_field("最近失败版本", &failed.version.id);
|
if let Some(failed) = historical_failures.last() {
|
||||||
print_field("最近失败时间", failed.failed_unix_seconds);
|
print_field("最近历史失败版本", &failed.version.id);
|
||||||
print_field("最近失败原因", &failed.error);
|
print_field("最近历史失败时间", failed.failed_unix_seconds);
|
||||||
|
print_field("最近历史失败原因", &failed.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visible_historical_failed_versions<'a>(
|
||||||
|
version_state: &'a OfficialVersionState,
|
||||||
|
) -> Vec<&'a OfficialFailedVersionRecord> {
|
||||||
|
let in_progress = version_state.in_progress_version.as_ref();
|
||||||
|
version_state
|
||||||
|
.failed_versions
|
||||||
|
.iter()
|
||||||
|
.filter(|failed| {
|
||||||
|
!in_progress.is_some_and(|version| version_matches_for_status(&failed.version, version))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version_matches_for_status(left: &OfficialVersionRecord, right: &OfficialVersionRecord) -> bool {
|
||||||
|
left.app_version == right.app_version
|
||||||
|
&& left.bundle_version == right.bundle_version
|
||||||
|
&& left.addressables_root == right.addressables_root
|
||||||
|
}
|
||||||
|
|
||||||
fn print_json_field(value: &serde_json::Value, key: &str, label: &str) {
|
fn print_json_field(value: &serde_json::Value, key: &str, label: &str) {
|
||||||
let Some(value) = value.get(key) else {
|
let Some(value) = value.get(key) else {
|
||||||
return;
|
return;
|
||||||
@@ -4400,6 +4421,43 @@ mod tests {
|
|||||||
assert!(!formatted.contains('{'));
|
assert!(!formatted.contains('{'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn daemon_status_hides_failure_superseded_by_in_progress_version() {
|
||||||
|
let version = OfficialVersionRecord {
|
||||||
|
id: "1.70.0-current-attempt".to_string(),
|
||||||
|
app_version: "1.70.0".to_string(),
|
||||||
|
bundle_version: Some("s8tloc7lo3".to_string()),
|
||||||
|
addressables_root: "https://prod-clientpatch.bluearchiveyostar.com/r93_token"
|
||||||
|
.to_string(),
|
||||||
|
resource_root: PathBuf::from("/tmp/bat/.staging/current"),
|
||||||
|
snapshot_path: PathBuf::from("/tmp/bat/.staging/current/official-sync-snapshot.json"),
|
||||||
|
staging_path: Some(PathBuf::from("/tmp/bat/.staging/current")),
|
||||||
|
version_path: None,
|
||||||
|
started_unix_seconds: Some(100),
|
||||||
|
completed_unix_seconds: None,
|
||||||
|
};
|
||||||
|
let older_failure = OfficialFailedVersionRecord {
|
||||||
|
version: OfficialVersionRecord {
|
||||||
|
id: "1.70.0-older-failure".to_string(),
|
||||||
|
resource_root: PathBuf::from("/tmp/bat/.staging/older"),
|
||||||
|
snapshot_path: PathBuf::from("/tmp/bat/.staging/older/official-sync-snapshot.json"),
|
||||||
|
staging_path: Some(PathBuf::from("/tmp/bat/.staging/older")),
|
||||||
|
started_unix_seconds: Some(90),
|
||||||
|
..version.clone()
|
||||||
|
},
|
||||||
|
error: "官方 hash 校验失败 fixture".to_string(),
|
||||||
|
failed_unix_seconds: 95,
|
||||||
|
};
|
||||||
|
let state = OfficialVersionState {
|
||||||
|
in_progress_version: Some(version),
|
||||||
|
failed_versions: vec![older_failure],
|
||||||
|
updated_unix_seconds: 100,
|
||||||
|
..OfficialVersionState::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(visible_historical_failed_versions(&state).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn daemon_socket_path_uses_state_dir() {
|
fn daemon_socket_path_uses_state_dir() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -2020,6 +2020,7 @@ fn prepare_in_progress_version_state(
|
|||||||
completed_unix_seconds: None,
|
completed_unix_seconds: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
remove_matching_version_failures(&mut state.failed_versions, &record);
|
||||||
state.in_progress_version = Some(record.clone());
|
state.in_progress_version = Some(record.clone());
|
||||||
state.updated_unix_seconds = now;
|
state.updated_unix_seconds = now;
|
||||||
write_version_state(path, &state)?;
|
write_version_state(path, &state)?;
|
||||||
@@ -2049,7 +2050,7 @@ fn complete_version_state(
|
|||||||
..record
|
..record
|
||||||
});
|
});
|
||||||
if let Some(current) = state.current_completed_version.as_ref() {
|
if let Some(current) = state.current_completed_version.as_ref() {
|
||||||
remove_completed_version_failures(&mut state.failed_versions, current);
|
remove_matching_version_failures(&mut state.failed_versions, current);
|
||||||
}
|
}
|
||||||
state.in_progress_version = None;
|
state.in_progress_version = None;
|
||||||
state.updated_unix_seconds = unix_seconds_now();
|
state.updated_unix_seconds = unix_seconds_now();
|
||||||
@@ -2132,9 +2133,10 @@ fn upsert_failed_version(
|
|||||||
error: &str,
|
error: &str,
|
||||||
failed_unix_seconds: u64,
|
failed_unix_seconds: u64,
|
||||||
) {
|
) {
|
||||||
if let Some(index) = failed_versions.iter().position(|failed| {
|
if let Some(index) = failed_versions
|
||||||
failed_version_matches(&failed.version, &record) && failed.error == error
|
.iter()
|
||||||
}) {
|
.position(|failed| failed_version_matches(&failed.version, &record))
|
||||||
|
{
|
||||||
failed_versions.remove(index);
|
failed_versions.remove(index);
|
||||||
}
|
}
|
||||||
failed_versions.push(OfficialFailedVersionRecord {
|
failed_versions.push(OfficialFailedVersionRecord {
|
||||||
@@ -2144,11 +2146,11 @@ fn upsert_failed_version(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_completed_version_failures(
|
fn remove_matching_version_failures(
|
||||||
failed_versions: &mut Vec<OfficialFailedVersionRecord>,
|
failed_versions: &mut Vec<OfficialFailedVersionRecord>,
|
||||||
completed: &OfficialVersionRecord,
|
version: &OfficialVersionRecord,
|
||||||
) {
|
) {
|
||||||
failed_versions.retain(|failed| !failed_version_matches(&failed.version, completed));
|
failed_versions.retain(|failed| !failed_version_matches(&failed.version, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed_version_matches(left: &OfficialVersionRecord, right: &OfficialVersionRecord) -> bool {
|
fn failed_version_matches(left: &OfficialVersionRecord, right: &OfficialVersionRecord) -> bool {
|
||||||
@@ -2844,7 +2846,9 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
fail_version_state(&path, failed, "download 404").unwrap();
|
fail_version_state(&path, failed, "download 404").unwrap();
|
||||||
let state = read_version_state(&path).unwrap().unwrap();
|
let state = read_version_state(&path).unwrap().unwrap();
|
||||||
assert_eq!(state.failed_versions.len(), 2);
|
assert_eq!(state.failed_versions.len(), 1);
|
||||||
|
assert_eq!(state.failed_versions[0].version.id, "broken-3");
|
||||||
|
assert_eq!(state.failed_versions[0].error, "download 404");
|
||||||
|
|
||||||
let staging = temp.path().join(".staging/fixed");
|
let staging = temp.path().join(".staging/fixed");
|
||||||
let fixed = prepare_in_progress_version_state(
|
let fixed = prepare_in_progress_version_state(
|
||||||
|
|||||||
Reference in New Issue
Block a user