From 87a4d4683df5f383906882d30fca74f457458174 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Wed, 23 Apr 2025 10:32:48 +0100 Subject: [PATCH 1/4] Add failing test --- src/fs.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/fs.rs b/src/fs.rs index d7cee44..8034270 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -400,6 +400,16 @@ mod tests { "virtualSegments": ["b/__virtual__/foo-abcdef/1/c/foo.zip", "c/foo.zip"], "zipPath": "bar" }], + ["/a/b/__virtual__/foo-abcdef/1/c/foo.zip/bar", { + "basePath": "/a", + "virtualSegments": ["b/__virtual__/foo-abcdef/1/c/foo.zip", "c/foo.zip"], + "zipPath": "bar" + }], + ["/a/b/__virtual__/foo-abcdef/2/c/foo.zip/bar", { + "basePath": "/", + "virtualSegments": ["a/b/__virtual__/foo-abcdef/2/c/foo.zip", "c/foo.zip"], + "zipPath": "bar" + }], ["./a/b/c/.zip", null], ["./a/b/c/foo.zipp", null], ["./a/b/c/foo.zip/bar/baz/qux.zip", { From b71b3cbe0bf46b5c2f2ac26fbcd5a8c5897fd458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Wed, 23 Apr 2025 14:08:30 +0200 Subject: [PATCH 2/4] Fixes failing test --- Cargo.lock | 175 +++++++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 1 + src/fs.rs | 170 ++++++++++++++++++++++++++------------------------- 3 files changed, 250 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ff181c..75cff6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,55 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "hashbrown" version = "0.12.3" @@ -455,6 +504,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pin-utils" version = "0.1.0" @@ -475,6 +530,7 @@ dependencies = [ "mmap-rs", "pathdiff", "regex", + "rstest", "serde", "serde_json", "serde_with", @@ -487,6 +543,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -498,9 +563,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -517,9 +582,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -529,9 +594,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -540,9 +605,54 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "ryu" @@ -559,6 +669,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.204" @@ -620,6 +736,15 @@ dependencies = [ "syn", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -634,9 +759,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.70" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -708,11 +833,28 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap 2.7.1", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "walkdir" @@ -940,3 +1082,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 5f43a89..3e3ecf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ miniz_oxide = "^0.7" mmap-rs = { version = "^0.6", optional = true } pathdiff = "^0.2" regex = "1" +rstest = "0.25.0" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_with = { version = "3", features = ["indexmap_2"] } diff --git a/src/fs.rs b/src/fs.rs index 8034270..1115485 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -207,7 +207,10 @@ fn split_zip(p_bytes: &[u8]) -> (&[u8], Option<&[u8]>) { fn split_virtual(p_bytes: &[u8]) -> std::io::Result<(usize, Option<(usize, usize)>)> { lazy_static! { - static ref VIRTUAL_RE: Regex = Regex::new("(?:^|/)((?:\\$\\$virtual|__virtual__)/(?:[^/]+)-[a-f0-9]+/([0-9]+)/)").unwrap(); + static ref VIRTUAL_RE: Regex + = Regex::new( + "(?:^|/)((?:\\$\\$virtual|__virtual__)/(?:[^/]+)-[a-f0-9]+/([0-9]+)/)" + ).unwrap(); } if let Some(m) = VIRTUAL_RE.captures(p_bytes) { @@ -261,7 +264,13 @@ fn vpath(p: &Path) -> std::io::Result { return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid virtual back-reference")) } - base_path_u8 = &archive_path_u8[0..base_path_len - 1]; + base_path_u8 + = &base_path_u8[0..base_path_len]; + + // Trim the trailing slash + if base_path_u8.len() > 1 { + base_path_u8 = &base_path_u8[0..base_path_u8.len() - 1]; + } virtual_segments = Some(( io_bytes_to_str(&archive_path_u8[base_path_len..archive_path_u8.len()])?.to_string(), @@ -287,9 +296,9 @@ fn vpath(p: &Path) -> std::io::Result { #[cfg(test)] mod tests { + use rstest::rstest; use std::path::PathBuf; - // Note this useful idiom: importing names from outer (for mod tests) scope. use super::*; #[test] @@ -362,87 +371,80 @@ mod tests { assert_eq!(res, "{\n \"name\": \"@babel/plugin-syntax-dynamic-import\",\n \"version\": \"7.8.3\",\n \"description\": \"Allow parsing of import()\",\n \"repository\": \"https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"main\": \"lib/index.js\",\n \"keywords\": [\n \"babel-plugin\"\n ],\n \"dependencies\": {\n \"@babel/helper-plugin-utils\": \"^7.8.0\"\n },\n \"peerDependencies\": {\n \"@babel/core\": \"^7.0.0-0\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.8.0\"\n }\n}\n"); } - #[test] - fn test_path_to_pnp() { - let tests: Vec<(String, Option)> = serde_json::from_str(r#"[ - [".zip", null], - ["foo", null], - ["foo.zip", null], - ["foo.zip/bar", { - "basePath": "foo.zip", - "virtualSegments": null, - "zipPath": "bar" - }], - ["foo.zip/bar/baz", { - "basePath": "foo.zip", - "virtualSegments": null, - "zipPath": "bar/baz" - }], - ["/a/b/c/foo.zip", null], - ["./a/b/c/foo.zip", null], - ["./a/b/__virtual__/foo-abcdef/0/c/d", { - "basePath": "a/b", - "virtualSegments": ["__virtual__/foo-abcdef/0/c/d", "c/d"], - "zipPath": null - }], - ["./a/b/__virtual__/foo-abcdef/1/c/d", { - "basePath": "a", - "virtualSegments": ["b/__virtual__/foo-abcdef/1/c/d", "c/d"], - "zipPath": null - }], - ["./a/b/__virtual__/foo-abcdef/0/c/foo.zip/bar", { - "basePath": "a/b", - "virtualSegments": ["__virtual__/foo-abcdef/0/c/foo.zip", "c/foo.zip"], - "zipPath": "bar" - }], - ["./a/b/__virtual__/foo-abcdef/1/c/foo.zip/bar", { - "basePath": "a", - "virtualSegments": ["b/__virtual__/foo-abcdef/1/c/foo.zip", "c/foo.zip"], - "zipPath": "bar" - }], - ["/a/b/__virtual__/foo-abcdef/1/c/foo.zip/bar", { - "basePath": "/a", - "virtualSegments": ["b/__virtual__/foo-abcdef/1/c/foo.zip", "c/foo.zip"], - "zipPath": "bar" - }], - ["/a/b/__virtual__/foo-abcdef/2/c/foo.zip/bar", { - "basePath": "/", - "virtualSegments": ["a/b/__virtual__/foo-abcdef/2/c/foo.zip", "c/foo.zip"], - "zipPath": "bar" - }], - ["./a/b/c/.zip", null], - ["./a/b/c/foo.zipp", null], - ["./a/b/c/foo.zip/bar/baz/qux.zip", { - "basePath": "a/b/c/foo.zip", - "virtualSegments": null, - "zipPath": "bar/baz/qux.zip" - }], - ["./a/b/c/foo.zip-bar.zip", null], - ["./a/b/c/foo.zip-bar.zip/bar/baz/qux.zip", { - "basePath": "a/b/c/foo.zip-bar.zip", - "virtualSegments": null, - "zipPath": "bar/baz/qux.zip" - }], - ["./a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip/d", { - "basePath": "a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip", - "virtualSegments": null, - "zipPath": "d" - }] - ]"#).expect("Assertion failed: Expected the expectations to be loaded"); - - for (input, expected) in tests.iter() { - let expectation: VPath = match expected { - Some(p) => p.clone(), - None => VPath::Native(PathBuf::from(arca::path::normalize_path(input))), - }; - - match vpath(&PathBuf::from(input)) { - Ok(res) => { - assert_eq!(res, expectation, "input='{:?}'", input); - } - Err(err) => { - panic!("{:?}: {}", input, err); - } + #[rstest] + #[case(".zip", None)] + #[case("foo", None)] + #[case("foo.zip", None)] + #[case("foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "foo.zip".into(), + virtual_segments: None, + zip_path: "bar".into(), + })))] + #[case("foo.zip/bar/baz", Some(VPath::Zip(ZipInfo { + base_path: "foo.zip".into(), + virtual_segments: None, + zip_path: "bar/baz".into(), + })))] + #[case("/a/b/c/foo.zip", None)] + #[case("./a/b/c/foo.zip", None)] + #[case("./a/b/__virtual__/foo-abcdef/0/c/d", Some(VPath::Virtual(VirtualInfo { + base_path: "a/b".into(), + virtual_segments: ("__virtual__/foo-abcdef/0/c/d".into(), "c/d".into()), + })))] + #[case("./a/b/__virtual__/foo-abcdef/1/c/d", Some(VPath::Virtual(VirtualInfo { + base_path: "a".into(), + virtual_segments: ("b/__virtual__/foo-abcdef/1/c/d".into(), "c/d".into()), + })))] + #[case("./a/b/__virtual__/foo-abcdef/0/c/foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "a/b".into(), + virtual_segments: Some(("__virtual__/foo-abcdef/0/c/foo.zip".into(), "c/foo.zip".into())), + zip_path: "bar".into(), + })))] + #[case("./a/b/__virtual__/foo-abcdef/1/c/foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "a".into(), + virtual_segments: Some(("b/__virtual__/foo-abcdef/1/c/foo.zip".into(), "c/foo.zip".into())), + zip_path: "bar".into(), + })))] + #[case("/a/b/__virtual__/foo-abcdef/1/c/foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "/a".into(), + virtual_segments: Some(("b/__virtual__/foo-abcdef/1/c/foo.zip".into(), "c/foo.zip".into())), + zip_path: "bar".into(), + })))] + #[case("/a/b/__virtual__/foo-abcdef/2/c/foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "/".into(), + virtual_segments: Some(("a/b/__virtual__/foo-abcdef/2/c/foo.zip".into(), "c/foo.zip".into())), + zip_path: "bar".into(), + })))] + #[case("./a/b/c/.zip", None)] + #[case("./a/b/c/foo.zipp", None)] + #[case("./a/b/c/foo.zip/bar/baz/qux.zip", Some(VPath::Zip(ZipInfo { + base_path: "a/b/c/foo.zip".into(), + virtual_segments: None, + zip_path: "bar/baz/qux.zip".into(), + })))] + #[case("./a/b/c/foo.zip-bar.zip", None)] + #[case("./a/b/c/foo.zip-bar.zip/bar/baz/qux.zip", Some(VPath::Zip(ZipInfo { + base_path: "a/b/c/foo.zip-bar.zip".into(), + virtual_segments: None, + zip_path: "bar/baz/qux.zip".into(), + })))] + #[case("./a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip/d", Some(VPath::Zip(ZipInfo { + base_path: "a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip".into(), + virtual_segments: None, + zip_path: "d".into(), + })))] + fn test_path_to_pnp(#[case] input: &str, #[case] expected: Option) { + let expectation: VPath = match &expected { + Some(p) => p.clone(), + None => VPath::Native(PathBuf::from(arca::path::normalize_path(input))), + }; + + match vpath(&PathBuf::from(input)) { + Ok(res) => { + assert_eq!(res, expectation, "input='{:?}'", input); + } + Err(err) => { + panic!("{:?}: {}", input, err); } } } From 831848655ef2186b78249a887af71b2529774f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Wed, 23 Apr 2025 14:13:05 +0200 Subject: [PATCH 3/4] Adds & fix another test case when __virtual__ is at the root of the path --- src/fs.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/fs.rs b/src/fs.rs index 1115485..e2b86d5 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -243,6 +243,10 @@ fn vpath(p: &Path) -> std::io::Result { if let Some((mut virtual_len, parent_depth)) = virtual_path_u8 { for _ in 0..parent_depth { + if base_path_len == 1 { + break; + } + base_path_len -= 1; virtual_len += 1; @@ -415,6 +419,11 @@ mod tests { virtual_segments: Some(("a/b/__virtual__/foo-abcdef/2/c/foo.zip".into(), "c/foo.zip".into())), zip_path: "bar".into(), })))] + #[case("/__virtual__/foo-abcdef/2/c/foo.zip/bar", Some(VPath::Zip(ZipInfo { + base_path: "/".into(), + virtual_segments: Some(("__virtual__/foo-abcdef/2/c/foo.zip".into(), "c/foo.zip".into())), + zip_path: "bar".into(), + })))] #[case("./a/b/c/.zip", None)] #[case("./a/b/c/foo.zipp", None)] #[case("./a/b/c/foo.zip/bar/baz/qux.zip", Some(VPath::Zip(ZipInfo { From 046014ff61b23633879c83f8cf0fe4d69ad2f661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Wed, 23 Apr 2025 14:15:20 +0200 Subject: [PATCH 4/4] Moves rstest to dev dependencies --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3e3ecf0..532f5ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,13 @@ miniz_oxide = "^0.7" mmap-rs = { version = "^0.6", optional = true } pathdiff = "^0.2" regex = "1" -rstest = "0.25.0" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_with = { version = "3", features = ["indexmap_2"] } thiserror = "1" +[dev-dependencies] +rstest = "0.25.0" + [features] mmap = ["dep:mmap-rs"]