Skip to content

Commit

Permalink
cargo-test-support: Make publish http api write to file system
Browse files Browse the repository at this point in the history
  • Loading branch information
Muscraft committed Sep 21, 2022
1 parent b91f732 commit c77cb34
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 83 deletions.
1 change: 1 addition & 0 deletions crates/cargo-test-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ doctest = false
anyhow = "1.0.34"
cargo-test-macro = { path = "../cargo-test-macro" }
cargo-util = { path = "../cargo-util" }
crates-io = { path = "../crates-io" }
snapbox = { version = "0.3.0", features = ["diff", "path"] }
filetime = "0.2"
flate2 = { version = "1.0", default-features = false, features = ["zlib"] }
Expand Down
90 changes: 89 additions & 1 deletion crates/cargo-test-support/src/publish.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::compare::{assert_match_exact, find_json_mismatch};
use crate::registry::{self, alt_api_path};
use crate::registry::{self, alt_api_path, FeatureMap};
use flate2::read::GzDecoder;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::File;
use std::io::{self, prelude::*, SeekFrom};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -155,3 +156,90 @@ pub fn validate_crate_contents(
}
}
}

pub(crate) fn create_index_line(
name: serde_json::Value,
vers: &str,
deps: Vec<serde_json::Value>,
cksum: &str,
features: crate::registry::FeatureMap,
yanked: bool,
links: Option<String>,
v: Option<u32>,
) -> String {
// This emulates what crates.io does to retain backwards compatibility.
let (features, features2) = split_index_features(features.clone());
let mut json = serde_json::json!({
"name": name,
"vers": vers,
"deps": deps,
"cksum": cksum,
"features": features,
"yanked": yanked,
"links": links,
});
if let Some(f2) = &features2 {
json["features2"] = serde_json::json!(f2);
json["v"] = serde_json::json!(2);
}
if let Some(v) = v {
json["v"] = serde_json::json!(v);
}

json.to_string()
}

pub(crate) fn write_to_index(registry_path: &PathBuf, name: &str, line: String, local: bool) {
let file = cargo_util::registry::make_dep_path(name, false);

// Write file/line in the index.
let dst = if local {
registry_path.join("index").join(&file)
} else {
registry_path.join(&file)
};
let prev = fs::read_to_string(&dst).unwrap_or_default();
t!(fs::create_dir_all(dst.parent().unwrap()));
t!(fs::write(&dst, prev + &line[..] + "\n"));

// Add the new file to the index.
if !local {
let repo = t!(git2::Repository::open(&registry_path));
let mut index = t!(repo.index());
t!(index.add_path(Path::new(&file)));
t!(index.write());
let id = t!(index.write_tree());

// Commit this change.
let tree = t!(repo.find_tree(id));
let sig = t!(repo.signature());
let parent = t!(repo.refname_to_id("refs/heads/master"));
let parent = t!(repo.find_commit(parent));
t!(repo.commit(
Some("HEAD"),
&sig,
&sig,
"Another commit",
&tree,
&[&parent]
));
}
}

fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
let mut features2 = FeatureMap::new();
for (feat, values) in features.iter_mut() {
if values
.iter()
.any(|value| value.starts_with("dep:") || value.contains("?/"))
{
let new_values = values.drain(..).collect();
features2.insert(feat.clone(), new_values);
}
}
if features2.is_empty() {
(features, None)
} else {
(features, Some(features2))
}
}
159 changes: 79 additions & 80 deletions crates/cargo-test-support/src/registry.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use crate::git::repo;
use crate::paths;
use crate::publish::{create_index_line, write_to_index};
use cargo_util::paths::append;
use cargo_util::{registry::make_dep_path, Sha256};
use cargo_util::Sha256;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::thread;
use tar::{Builder, Header};
use url::Url;
Expand Down Expand Up @@ -389,7 +390,7 @@ pub struct Package {
v: Option<u32>,
}

type FeatureMap = BTreeMap<String, Vec<String>>;
pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;

#[derive(Clone)]
pub struct Dependency {
Expand Down Expand Up @@ -637,16 +638,74 @@ impl HttpServer {
self.dl(&req)
}
}
// publish
("put", ["api", "v1", "crates", "new"]) => {
if !authorized(true) {
return self.unauthorized(req)
}
if let Some(body) = &req.body {
// Get the metadata of the package
let (len, remaining) = body.split_at(4);
let json_len = u32::from_le_bytes(len.try_into().unwrap());
let (json, remaining) = remaining.split_at(json_len as usize);
let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
// Get the `.crate` file
let (len, remaining) = remaining.split_at(4);
let file_len = u32::from_le_bytes(len.try_into().unwrap());
let (file, _remaining) = remaining.split_at(file_len as usize);

// Write the `.crate`
let dst = self.dl_path.join(&new_crate.name).join(&new_crate.vers).join("download");
t!(fs::create_dir_all(dst.parent().unwrap()));
t!(fs::write(&dst, file));

let deps = new_crate.deps.iter().map(|dep| {
let (name, package) = match &dep.explicit_name_in_toml {
Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
None => (dep.name.to_string(), None),
};
serde_json::json!({
"name": name,
"req": dep.version_req,
"features": dep.features,
"default_features": true,
"target": dep.target,
"optional": dep.optional,
"kind": dep.kind,
"registry": dep.registry,
"package": package,
})
}).collect::<Vec<_>>();

let line = create_index_line(
serde_json::json!(new_crate.name),
&new_crate.vers,
deps,
&cksum(file),
new_crate.features,
false,
new_crate.links,
None,
);

write_to_index(&self.registry_path, &new_crate.name, line, false);

self.ok(&req)
} else {
Response {
code: 400,
headers: vec![],
body: b"The request was missing a body".to_vec(),
}
}
}
// The remainder of the operators in the test framework do nothing other than responding 'ok'.
//
// Note: We don't need to support anything real here because the testing framework publishes crates
// by writing directly to the filesystem instead. If the test framework is changed to publish
// via the HTTP API, then this should be made more complete.
// Note: We don't need to support anything real here because there are no tests that
// currently require anything other than publishing via the http api.

// publish
("put", ["api", "v1", "crates", "new"])
// yank
| ("delete", ["api", "v1", "crates", .., "yank"])
("delete", ["api", "v1", "crates", .., "yank"])
// unyank
| ("put", ["api", "v1", "crates", .., "unyank"])
// owners
Expand Down Expand Up @@ -999,66 +1058,24 @@ impl Package {
} else {
serde_json::json!(self.name)
};
// This emulates what crates.io may do in the future.
let (features, features2) = split_index_features(self.features.clone());
let mut json = serde_json::json!({
"name": name,
"vers": self.vers,
"deps": deps,
"cksum": cksum,
"features": features,
"yanked": self.yanked,
"links": self.links,
});
if let Some(f2) = &features2 {
json["features2"] = serde_json::json!(f2);
json["v"] = serde_json::json!(2);
}
if let Some(v) = self.v {
json["v"] = serde_json::json!(v);
}
let line = json.to_string();

let file = make_dep_path(&self.name, false);
let line = create_index_line(
name,
&self.vers,
deps,
&cksum,
self.features.clone(),
self.yanked,
self.links.clone(),
self.v,
);

let registry_path = if self.alternative {
alt_registry_path()
} else {
registry_path()
};

// Write file/line in the index.
let dst = if self.local {
registry_path.join("index").join(&file)
} else {
registry_path.join(&file)
};
let prev = fs::read_to_string(&dst).unwrap_or_default();
t!(fs::create_dir_all(dst.parent().unwrap()));
t!(fs::write(&dst, prev + &line[..] + "\n"));

// Add the new file to the index.
if !self.local {
let repo = t!(git2::Repository::open(&registry_path));
let mut index = t!(repo.index());
t!(index.add_path(Path::new(&file)));
t!(index.write());
let id = t!(index.write_tree());

// Commit this change.
let tree = t!(repo.find_tree(id));
let sig = t!(repo.signature());
let parent = t!(repo.refname_to_id("refs/heads/master"));
let parent = t!(repo.find_commit(parent));
t!(repo.commit(
Some("HEAD"),
&sig,
&sig,
"Another commit",
&tree,
&[&parent]
));
}
write_to_index(&registry_path, &self.name, line, self.local);

cksum
}
Expand Down Expand Up @@ -1279,21 +1296,3 @@ impl Dependency {
self
}
}

fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
let mut features2 = FeatureMap::new();
for (feat, values) in features.iter_mut() {
if values
.iter()
.any(|value| value.starts_with("dep:") || value.contains("?/"))
{
let new_values = values.drain(..).collect();
features2.insert(feat.clone(), new_values);
}
}
if features2.is_empty() {
(features, None)
} else {
(features, Some(features2))
}
}
4 changes: 2 additions & 2 deletions crates/crates-io/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct Crate {
pub max_version: String,
}

#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
pub struct NewCrate {
pub name: String,
pub vers: String,
Expand All @@ -57,7 +57,7 @@ pub struct NewCrate {
pub links: Option<String>,
}

#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
pub struct NewCrateDependency {
pub optional: bool,
pub default_features: bool,
Expand Down
42 changes: 42 additions & 0 deletions tests/testsuite/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2048,3 +2048,45 @@ error: package ID specification `bar` did not match any packages
)
.run();
}

#[cargo_test]
fn http_api_not_noop() {
let _registry = registry::RegistryBuilder::new().http_api().build();

let p = project()
.file(
"Cargo.toml",
r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
license = "MIT"
description = "foo"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("publish --token api-token").run();

let p = project()
.file(
"Cargo.toml",
r#"
[project]
name = "bar"
version = "0.0.1"
authors = []
license = "MIT"
description = "foo"
[dependencies]
foo = "0.0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("build").run();
}

0 comments on commit c77cb34

Please sign in to comment.