Skip to content
Open
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
68 changes: 68 additions & 0 deletions src/dist/manifestation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::{
collections::HashMap,
env, fs,
io::Write,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
Expand All @@ -14,6 +15,7 @@ use anyhow::{Result, anyhow};
use url::Url;

use crate::{
config::Cfg,
dist::{
DEFAULT_DIST_SERVER, Profile, TargetTriple, ToolchainDesc,
download::{DownloadCfg, DownloadTracker},
Expand All @@ -26,8 +28,10 @@ use crate::{
errors::RustupError,
process::TestProcess,
test::{
Env, RustupHome,
dist::*,
mock::{MockComponentBuilder, MockFile, MockInstallerBuilder},
test_dir,
},
utils::{self, raw as utils_raw},
};
Expand Down Expand Up @@ -1529,3 +1533,67 @@ async fn handle_corrupt_partial_downloads() {
assert!(utils::path_exists(cx.prefix.path().join("bin/rustc")));
assert!(utils::path_exists(cx.prefix.path().join("lib/libstd.rlib")));
}

/// If the v2 channel manifest bytes do not match the published `.sha256`, the failure must be
/// reported as an error (not treated as "no update" / `Ok(None)`).
#[tokio::test]
async fn v2_manifest_checksum_mismatch_surfaces_error() {
let cx = TestContext::new(None, GZOnly);
let mock_root = cx.url.to_file_path().unwrap();
let manifest_path = mock_root.join("dist/channel-rust-nightly.toml");
fs::OpenOptions::new()
.append(true)
.open(&manifest_path)
.unwrap()
.write_all(b"\n# test: corrupt manifest body vs .sha256\n")
.unwrap();

let root = test_dir().unwrap();
let rustup_home = RustupHome::new_in(root.path()).unwrap();
let cargo_home = tempfile::Builder::new()
.prefix("cargo")
.tempdir_in(root.path())
.unwrap();
let home = tempfile::Builder::new()
.prefix("home")
.tempdir_in(root.path())
.unwrap();

let mut vars = HashMap::new();
rustup_home.apply(&mut vars);
vars.env(
"CARGO_HOME",
cargo_home.path().to_string_lossy().to_string(),
);
vars.env("HOME", home.path().to_string_lossy().to_string());
vars.env("RUSTUP_DIST_SERVER", cx.url.as_str());

let tp = TestProcess::new(env::current_dir().unwrap(), &["rustup"], vars, "");
let cfg = Cfg::from_env(tp.process.current_dir().unwrap(), false, &tp.process).unwrap();
let dl_cfg = DownloadCfg::new(&cfg);
let update_hash = cfg.get_hash_file(&cx.toolchain, true).unwrap();
let mut fetched = String::new();

let err = super::super::try_update_from_dist_(
&dl_cfg,
&update_hash,
&cx.toolchain,
Some(Profile::Default),
&cx.prefix,
false,
&[],
&[],
&mut fetched,
&cfg,
None,
)
.await
.unwrap_err();

match err.downcast_ref::<RustupError>() {
Some(RustupError::ChecksumFailed { .. }) => {}
e => {
panic!("expected ChecksumFailed for corrupt v2 manifest, got {e:?} full error: {err:?}")
}
}
}
6 changes: 5 additions & 1 deletion src/dist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1234,11 +1234,15 @@ async fn try_update_from_dist_(
Ok(None) => return Ok(None),
Err(err) => {
match err.downcast_ref::<RustupError>() {
Some(RustupError::ChecksumFailed { .. }) => return Ok(None),
Some(RustupError::DownloadNotExists { .. }) => {
// Proceed to try v1 as a fallback
debug!("manifest not found; trying legacy manifest");
}
// Includes `ChecksumFailed`: if the v2 manifest exists but its
// contents do not match the published `.sha256`, surface the
// integrity failure as an error rather than silently treating
// the toolchain as up to date. The v1 fallback path below
// already does the same.
_ => return Err(err),
}
}
Expand Down
Loading