chore: Move all integration tests to a single binary (#8093)

As per
https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html

Before that, there were 91 separate integration tests binary.

(As discussed on Discord — I've done the `uv` crate, there's still a few
more commits coming before this is mergeable, and I want to see how it
performs in CI and locally).
This commit is contained in:
Amos Wenger 2024-10-11 16:41:35 +02:00 committed by GitHub
parent fce7a838e9
commit 715f28fd39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
231 changed files with 15585 additions and 15507 deletions

View file

@ -498,142 +498,4 @@ fn write_record(
}
#[cfg(test)]
mod tests {
use super::*;
use insta::{assert_snapshot, with_settings};
use std::str::FromStr;
use tempfile::TempDir;
use uv_normalize::PackageName;
use uv_pep440::Version;
#[test]
fn test_wheel() {
let filename = WheelFilename {
name: PackageName::from_str("foo").unwrap(),
version: Version::from_str("1.2.3").unwrap(),
build_tag: None,
python_tag: vec!["py2".to_string(), "py3".to_string()],
abi_tag: vec!["none".to_string()],
platform_tag: vec!["any".to_string()],
};
with_settings!({
filters => [(uv_version::version(), "[VERSION]")],
}, {
assert_snapshot!(wheel_info(&filename), @r"
Wheel-Version: 1.0
Generator: uv [VERSION]
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
");
});
}
#[test]
fn test_record() {
let record = vec![RecordEntry {
path: "uv_backend/__init__.py".to_string(),
hash: "89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865".to_string(),
size: 37,
}];
let mut writer = Vec::new();
write_record(&mut writer, "uv_backend-0.1.0", record).unwrap();
assert_snapshot!(String::from_utf8(writer).unwrap(), @r"
uv_backend/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37
uv_backend-0.1.0/RECORD,,
");
}
/// Check that we write deterministic wheels.
#[test]
fn test_determinism() {
let temp1 = TempDir::new().unwrap();
let uv_backend = Path::new("../../scripts/packages/uv_backend");
build(uv_backend, temp1.path(), None).unwrap();
// Touch the file to check that we don't serialize the last modified date.
fs_err::write(
uv_backend.join("src/uv_backend/__init__.py"),
"def greet():\n print(\"Hello 👋\")\n",
)
.unwrap();
let temp2 = TempDir::new().unwrap();
build(uv_backend, temp2.path(), None).unwrap();
let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl";
assert_eq!(
fs_err::read(temp1.path().join(wheel_filename)).unwrap(),
fs_err::read(temp2.path().join(wheel_filename)).unwrap()
);
}
/// Snapshot all files from the prepare metadata hook.
#[test]
fn test_prepare_metadata() {
let metadata_dir = TempDir::new().unwrap();
let uv_backend = Path::new("../../scripts/packages/uv_backend");
metadata(uv_backend, metadata_dir.path()).unwrap();
let mut files: Vec<_> = WalkDir::new(metadata_dir.path())
.into_iter()
.map(|entry| {
entry
.unwrap()
.path()
.strip_prefix(metadata_dir.path())
.unwrap()
.portable_display()
.to_string()
})
.filter(|path| !path.is_empty())
.collect();
files.sort();
assert_snapshot!(files.join("\n"), @r"
uv_backend-0.1.0.dist-info
uv_backend-0.1.0.dist-info/METADATA
uv_backend-0.1.0.dist-info/RECORD
uv_backend-0.1.0.dist-info/WHEEL
");
let metadata_file = metadata_dir
.path()
.join("uv_backend-0.1.0.dist-info/METADATA");
assert_snapshot!(fs_err::read_to_string(metadata_file).unwrap(), @r###"
Metadata-Version: 2.3
Name: uv-backend
Version: 0.1.0
Summary: Add your description here
Requires-Python: >=3.12
Description-Content-Type: text/markdown
# uv_backend
A simple package to be built with the uv build backend.
"###);
let record_file = metadata_dir
.path()
.join("uv_backend-0.1.0.dist-info/RECORD");
assert_snapshot!(fs_err::read_to_string(record_file).unwrap(), @r###"
uv_backend-0.1.0.dist-info/WHEEL,sha256=70ce44709b6a53e0d0c5a6755b0290179697020f1f867e794f26154fe4825738,79
uv_backend-0.1.0.dist-info/METADATA,sha256=e4a0d390317d7182f65ea978254c71ed283e0a4242150cf1c99a694b113ff68d,224
uv_backend-0.1.0.dist-info/RECORD,,
"###);
let wheel_file = metadata_dir.path().join("uv_backend-0.1.0.dist-info/WHEEL");
let filters = vec![(uv_version::version(), "[VERSION]")];
with_settings!({
filters => filters
}, {
assert_snapshot!(fs_err::read_to_string(wheel_file).unwrap(), @r###"
Wheel-Version: 1.0
Generator: uv [VERSION]
Root-Is-Purelib: true
Tag: py3-none-any
"###);
});
}
}
mod tests;

View file

@ -629,406 +629,4 @@ struct BuildSystem {
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::{formatdoc, indoc};
use insta::assert_snapshot;
use std::iter;
use tempfile::TempDir;
fn extend_project(payload: &str) -> String {
formatdoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
{payload}
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "uv"
"#
}
}
fn format_err(err: impl std::error::Error) -> String {
let mut formatted = err.to_string();
for source in iter::successors(err.source(), |&err| err.source()) {
formatted += &format!("\n Caused by: {source}");
}
formatted
}
#[test]
fn valid() {
let temp_dir = TempDir::new().unwrap();
fs_err::write(
temp_dir.path().join("Readme.md"),
indoc! {r"
# Foo
This is the foo library.
"},
)
.unwrap();
fs_err::write(
temp_dir.path().join("License.txt"),
indoc! {r#"
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"#},
)
.unwrap();
let contents = indoc! {r#"
# See https://github.com/pypa/sampleproject/blob/main/pyproject.toml for another example
[project]
name = "hello-world"
version = "0.1.0"
description = "A Python package"
readme = "Readme.md"
requires_python = ">=3.12"
license = { file = "License.txt" }
authors = [{ name = "Ferris the crab", email = "ferris@rustacean.net" }]
maintainers = [{ name = "Konsti", email = "konstin@mailbox.org" }]
keywords = ["demo", "example", "package"]
classifiers = [
"Development Status :: 6 - Mature",
"License :: OSI Approved :: MIT License",
# https://github.com/pypa/trove-classifiers/issues/17
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
]
dependencies = ["flask>=3,<4", "sqlalchemy[asyncio]>=2.0.35,<3"]
# We don't support dynamic fields, the default empty array is the only allowed value.
dynamic = []
[project.optional-dependencies]
postgres = ["psycopg>=3.2.2,<4"]
mysql = ["pymysql>=1.1.1,<2"]
[project.urls]
"Homepage" = "https://github.com/astral-sh/uv"
"Repository" = "https://astral.sh"
[project.scripts]
foo = "foo.cli:__main__"
[project.gui-scripts]
foo-gui = "foo.gui"
[project.entry-points.bar_group]
foo-bar = "foo:bar"
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "uv"
"#
};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.3
Name: hello-world
Version: 0.1.0
Summary: A Python package
Keywords: demo,example,package
Author: Ferris the crab
License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Classifier: Development Status :: 6 - Mature
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python
Requires-Dist: flask>=3,<4
Requires-Dist: sqlalchemy[asyncio]>=2.0.35,<3
Maintainer: Konsti
Project-URL: Homepage, https://github.com/astral-sh/uv
Project-URL: Repository, https://astral.sh
Provides-Extra: mysql
Provides-Extra: postgres
Description-Content-Type: text/markdown
# Foo
This is the foo library.
"###);
assert_snapshot!(pyproject_toml.to_entry_points().unwrap().unwrap(), @r###"
[console_scripts]
foo = foo.cli:__main__
[gui_scripts]
foo-gui = foo.gui
[bar_group]
foo-bar = foo:bar
"###);
}
#[test]
fn build_system_valid() {
let contents = extend_project("");
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
assert!(pyproject_toml.check_build_system());
}
#[test]
fn build_system_no_bound() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_multiple_packages() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv>=0.4.15,<5", "wheel"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_no_requires_uv() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["setuptools"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_not_uv() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "setuptools"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn minimal() {
let contents = extend_project("");
let metadata = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.3
Name: hello-world
Version: 0.1.0
"###);
}
#[test]
fn invalid_readme_spec() {
let contents = extend_project(indoc! {r#"
readme = { path = "Readme.md" }
"#
});
let err = PyProjectToml::parse(&contents).unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: TOML parse error at line 4, column 10
|
4 | readme = { path = "Readme.md" }
| ^^^^^^^^^^^^^^^^^^^^^^
data did not match any variant of untagged enum Readme
"###);
}
#[test]
fn missing_readme() {
let contents = extend_project(indoc! {r#"
readme = "Readme.md"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
// Simplified for windows compatibility.
assert_snapshot!(err.to_string().replace('\\', "/"), @"failed to open file `/do/not/read/Readme.md`");
}
#[test]
fn multiline_description() {
let contents = extend_project(indoc! {r#"
description = "Hi :)\nThis is my project"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: `project.description` must be a single line
"###);
}
#[test]
fn mixed_licenses() {
let contents = extend_project(indoc! {r#"
license-files = ["licenses/*"]
license = { text = "MIT" }
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: When `project.license-files` is defined, `project.license` must be an SPDX expression string
"###);
}
#[test]
fn valid_license() {
let contents = extend_project(indoc! {r#"
license = "MIT OR Apache-2.0"
"#
});
let metadata = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.4
Name: hello-world
Version: 0.1.0
License-Expression: MIT OR Apache-2.0
"###);
}
#[test]
fn invalid_license() {
let contents = extend_project(indoc! {r#"
license = "MIT XOR Apache-2"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
// TODO(konsti): We mess up the indentation in the error.
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: `project.license` is not a valid SPDX expression: `MIT XOR Apache-2`
Caused by: MIT XOR Apache-2
^^^ unknown term
"###);
}
#[test]
fn dynamic() {
let contents = extend_project(indoc! {r#"
dynamic = ["dependencies"]
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: Dynamic metadata is not supported
"###);
}
fn script_error(contents: &str) -> String {
let err = PyProjectToml::parse(contents)
.unwrap()
.to_entry_points()
.unwrap_err();
format_err(err)
}
#[test]
fn invalid_entry_point_group() {
let contents = extend_project(indoc! {r#"
[project.entry-points."a@b"]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`");
}
#[test]
fn invalid_entry_point_name() {
let contents = extend_project(indoc! {r#"
[project.scripts]
"a@b" = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint names must consist of letters, numbers, dots and dashes; invalid name: `a@b`");
}
#[test]
fn invalid_entry_point_conflict_scripts() {
let contents = extend_project(indoc! {r#"
[project.entry-points.console_scripts]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Use `project.scripts` instead of `project.entry-points.console_scripts`");
}
#[test]
fn invalid_entry_point_conflict_gui_scripts() {
let contents = extend_project(indoc! {r#"
[project.entry-points.gui_scripts]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`");
}
}
mod tests;

View file

@ -0,0 +1,401 @@
use super::*;
use indoc::{formatdoc, indoc};
use insta::assert_snapshot;
use std::iter;
use tempfile::TempDir;
fn extend_project(payload: &str) -> String {
formatdoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
{payload}
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "uv"
"#
}
}
fn format_err(err: impl std::error::Error) -> String {
let mut formatted = err.to_string();
for source in iter::successors(err.source(), |&err| err.source()) {
formatted += &format!("\n Caused by: {source}");
}
formatted
}
#[test]
fn valid() {
let temp_dir = TempDir::new().unwrap();
fs_err::write(
temp_dir.path().join("Readme.md"),
indoc! {r"
# Foo
This is the foo library.
"},
)
.unwrap();
fs_err::write(
temp_dir.path().join("License.txt"),
indoc! {r#"
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"#},
)
.unwrap();
let contents = indoc! {r#"
# See https://github.com/pypa/sampleproject/blob/main/pyproject.toml for another example
[project]
name = "hello-world"
version = "0.1.0"
description = "A Python package"
readme = "Readme.md"
requires_python = ">=3.12"
license = { file = "License.txt" }
authors = [{ name = "Ferris the crab", email = "ferris@rustacean.net" }]
maintainers = [{ name = "Konsti", email = "konstin@mailbox.org" }]
keywords = ["demo", "example", "package"]
classifiers = [
"Development Status :: 6 - Mature",
"License :: OSI Approved :: MIT License",
# https://github.com/pypa/trove-classifiers/issues/17
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
]
dependencies = ["flask>=3,<4", "sqlalchemy[asyncio]>=2.0.35,<3"]
# We don't support dynamic fields, the default empty array is the only allowed value.
dynamic = []
[project.optional-dependencies]
postgres = ["psycopg>=3.2.2,<4"]
mysql = ["pymysql>=1.1.1,<2"]
[project.urls]
"Homepage" = "https://github.com/astral-sh/uv"
"Repository" = "https://astral.sh"
[project.scripts]
foo = "foo.cli:__main__"
[project.gui-scripts]
foo-gui = "foo.gui"
[project.entry-points.bar_group]
foo-bar = "foo:bar"
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "uv"
"#
};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.3
Name: hello-world
Version: 0.1.0
Summary: A Python package
Keywords: demo,example,package
Author: Ferris the crab
License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Classifier: Development Status :: 6 - Mature
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python
Requires-Dist: flask>=3,<4
Requires-Dist: sqlalchemy[asyncio]>=2.0.35,<3
Maintainer: Konsti
Project-URL: Homepage, https://github.com/astral-sh/uv
Project-URL: Repository, https://astral.sh
Provides-Extra: mysql
Provides-Extra: postgres
Description-Content-Type: text/markdown
# Foo
This is the foo library.
"###);
assert_snapshot!(pyproject_toml.to_entry_points().unwrap().unwrap(), @r###"
[console_scripts]
foo = foo.cli:__main__
[gui_scripts]
foo-gui = foo.gui
[bar_group]
foo-bar = foo:bar
"###);
}
#[test]
fn build_system_valid() {
let contents = extend_project("");
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
assert!(pyproject_toml.check_build_system());
}
#[test]
fn build_system_no_bound() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_multiple_packages() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv>=0.4.15,<5", "wheel"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_no_requires_uv() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["setuptools"]
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn build_system_not_uv() {
let contents = indoc! {r#"
[project]
name = "hello-world"
version = "0.1.0"
[build-system]
requires = ["uv>=0.4.15,<5"]
build-backend = "setuptools"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system());
}
#[test]
fn minimal() {
let contents = extend_project("");
let metadata = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.3
Name: hello-world
Version: 0.1.0
"###);
}
#[test]
fn invalid_readme_spec() {
let contents = extend_project(indoc! {r#"
readme = { path = "Readme.md" }
"#
});
let err = PyProjectToml::parse(&contents).unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: TOML parse error at line 4, column 10
|
4 | readme = { path = "Readme.md" }
| ^^^^^^^^^^^^^^^^^^^^^^
data did not match any variant of untagged enum Readme
"###);
}
#[test]
fn missing_readme() {
let contents = extend_project(indoc! {r#"
readme = "Readme.md"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
// Simplified for windows compatibility.
assert_snapshot!(err.to_string().replace('\\', "/"), @"failed to open file `/do/not/read/Readme.md`");
}
#[test]
fn multiline_description() {
let contents = extend_project(indoc! {r#"
description = "Hi :)\nThis is my project"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: `project.description` must be a single line
"###);
}
#[test]
fn mixed_licenses() {
let contents = extend_project(indoc! {r#"
license-files = ["licenses/*"]
license = { text = "MIT" }
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: When `project.license-files` is defined, `project.license` must be an SPDX expression string
"###);
}
#[test]
fn valid_license() {
let contents = extend_project(indoc! {r#"
license = "MIT OR Apache-2.0"
"#
});
let metadata = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
Metadata-Version: 2.4
Name: hello-world
Version: 0.1.0
License-Expression: MIT OR Apache-2.0
"###);
}
#[test]
fn invalid_license() {
let contents = extend_project(indoc! {r#"
license = "MIT XOR Apache-2"
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
// TODO(konsti): We mess up the indentation in the error.
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: `project.license` is not a valid SPDX expression: `MIT XOR Apache-2`
Caused by: MIT XOR Apache-2
^^^ unknown term
"###);
}
#[test]
fn dynamic() {
let contents = extend_project(indoc! {r#"
dynamic = ["dependencies"]
"#
});
let err = PyProjectToml::parse(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: Dynamic metadata is not supported
"###);
}
fn script_error(contents: &str) -> String {
let err = PyProjectToml::parse(contents)
.unwrap()
.to_entry_points()
.unwrap_err();
format_err(err)
}
#[test]
fn invalid_entry_point_group() {
let contents = extend_project(indoc! {r#"
[project.entry-points."a@b"]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`");
}
#[test]
fn invalid_entry_point_name() {
let contents = extend_project(indoc! {r#"
[project.scripts]
"a@b" = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint names must consist of letters, numbers, dots and dashes; invalid name: `a@b`");
}
#[test]
fn invalid_entry_point_conflict_scripts() {
let contents = extend_project(indoc! {r#"
[project.entry-points.console_scripts]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Use `project.scripts` instead of `project.entry-points.console_scripts`");
}
#[test]
fn invalid_entry_point_conflict_gui_scripts() {
let contents = extend_project(indoc! {r#"
[project.entry-points.gui_scripts]
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`");
}

View file

@ -78,59 +78,4 @@ pub(crate) fn parse_pep639_glob(glob: &str) -> Result<Pattern, Pep639GlobError>
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
#[test]
fn test_error() {
let parse_err = |glob| parse_pep639_glob(glob).unwrap_err().to_string();
assert_snapshot!(
parse_err(".."),
@"The parent directory operator (`..`) at position 0 is not allowed in license file globs"
);
assert_snapshot!(
parse_err("licenses/.."),
@"The parent directory operator (`..`) at position 9 is not allowed in license file globs"
);
assert_snapshot!(
parse_err("licenses/LICEN!E.txt"),
@"Glob contains invalid character at position 14: `!`"
);
assert_snapshot!(
parse_err("licenses/LICEN[!C]E.txt"),
@"Glob contains invalid character in range at position 15: `!`"
);
assert_snapshot!(
parse_err("licenses/LICEN[C?]E.txt"),
@"Glob contains invalid character in range at position 16: `?`"
);
assert_snapshot!(parse_err("******"), @"Pattern syntax error near position 2: wildcards are either regular `*` or recursive `**`");
assert_snapshot!(
parse_err(r"licenses\eula.txt"),
@r"Glob contains invalid character at position 8: `\`"
);
}
#[test]
fn test_valid() {
let cases = [
"licenses/*.txt",
"licenses/**/*.txt",
"LICEN[CS]E.txt",
"LICEN?E.txt",
"[a-z].txt",
"[a-z._-].txt",
"*/**",
"LICENSE..txt",
"LICENSE_file-1.txt",
// (google translate)
"licenses/라이센스*.txt",
"licenses/ライセンス*.txt",
"licenses/执照*.txt",
];
for case in cases {
parse_pep639_glob(case).unwrap();
}
}
}
mod tests;

View file

@ -0,0 +1,54 @@
use super::*;
use insta::assert_snapshot;
#[test]
fn test_error() {
let parse_err = |glob| parse_pep639_glob(glob).unwrap_err().to_string();
assert_snapshot!(
parse_err(".."),
@"The parent directory operator (`..`) at position 0 is not allowed in license file globs"
);
assert_snapshot!(
parse_err("licenses/.."),
@"The parent directory operator (`..`) at position 9 is not allowed in license file globs"
);
assert_snapshot!(
parse_err("licenses/LICEN!E.txt"),
@"Glob contains invalid character at position 14: `!`"
);
assert_snapshot!(
parse_err("licenses/LICEN[!C]E.txt"),
@"Glob contains invalid character in range at position 15: `!`"
);
assert_snapshot!(
parse_err("licenses/LICEN[C?]E.txt"),
@"Glob contains invalid character in range at position 16: `?`"
);
assert_snapshot!(parse_err("******"), @"Pattern syntax error near position 2: wildcards are either regular `*` or recursive `**`");
assert_snapshot!(
parse_err(r"licenses\eula.txt"),
@r"Glob contains invalid character at position 8: `\`"
);
}
#[test]
fn test_valid() {
let cases = [
"licenses/*.txt",
"licenses/**/*.txt",
"LICEN[CS]E.txt",
"LICEN?E.txt",
"[a-z].txt",
"[a-z._-].txt",
"*/**",
"LICENSE..txt",
"LICENSE_file-1.txt",
// (google translate)
"licenses/라이센스*.txt",
"licenses/ライセンス*.txt",
"licenses/执照*.txt",
];
for case in cases {
parse_pep639_glob(case).unwrap();
}
}

View file

@ -0,0 +1,137 @@
use super::*;
use insta::{assert_snapshot, with_settings};
use std::str::FromStr;
use tempfile::TempDir;
use uv_normalize::PackageName;
use uv_pep440::Version;
#[test]
fn test_wheel() {
let filename = WheelFilename {
name: PackageName::from_str("foo").unwrap(),
version: Version::from_str("1.2.3").unwrap(),
build_tag: None,
python_tag: vec!["py2".to_string(), "py3".to_string()],
abi_tag: vec!["none".to_string()],
platform_tag: vec!["any".to_string()],
};
with_settings!({
filters => [(uv_version::version(), "[VERSION]")],
}, {
assert_snapshot!(wheel_info(&filename), @r"
Wheel-Version: 1.0
Generator: uv [VERSION]
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
");
});
}
#[test]
fn test_record() {
let record = vec![RecordEntry {
path: "uv_backend/__init__.py".to_string(),
hash: "89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865".to_string(),
size: 37,
}];
let mut writer = Vec::new();
write_record(&mut writer, "uv_backend-0.1.0", record).unwrap();
assert_snapshot!(String::from_utf8(writer).unwrap(), @r"
uv_backend/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37
uv_backend-0.1.0/RECORD,,
");
}
/// Check that we write deterministic wheels.
#[test]
fn test_determinism() {
let temp1 = TempDir::new().unwrap();
let uv_backend = Path::new("../../scripts/packages/uv_backend");
build(uv_backend, temp1.path(), None).unwrap();
// Touch the file to check that we don't serialize the last modified date.
fs_err::write(
uv_backend.join("src/uv_backend/__init__.py"),
"def greet():\n print(\"Hello 👋\")\n",
)
.unwrap();
let temp2 = TempDir::new().unwrap();
build(uv_backend, temp2.path(), None).unwrap();
let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl";
assert_eq!(
fs_err::read(temp1.path().join(wheel_filename)).unwrap(),
fs_err::read(temp2.path().join(wheel_filename)).unwrap()
);
}
/// Snapshot all files from the prepare metadata hook.
#[test]
fn test_prepare_metadata() {
let metadata_dir = TempDir::new().unwrap();
let uv_backend = Path::new("../../scripts/packages/uv_backend");
metadata(uv_backend, metadata_dir.path()).unwrap();
let mut files: Vec<_> = WalkDir::new(metadata_dir.path())
.into_iter()
.map(|entry| {
entry
.unwrap()
.path()
.strip_prefix(metadata_dir.path())
.unwrap()
.portable_display()
.to_string()
})
.filter(|path| !path.is_empty())
.collect();
files.sort();
assert_snapshot!(files.join("\n"), @r"
uv_backend-0.1.0.dist-info
uv_backend-0.1.0.dist-info/METADATA
uv_backend-0.1.0.dist-info/RECORD
uv_backend-0.1.0.dist-info/WHEEL
");
let metadata_file = metadata_dir
.path()
.join("uv_backend-0.1.0.dist-info/METADATA");
assert_snapshot!(fs_err::read_to_string(metadata_file).unwrap(), @r###"
Metadata-Version: 2.3
Name: uv-backend
Version: 0.1.0
Summary: Add your description here
Requires-Python: >=3.12
Description-Content-Type: text/markdown
# uv_backend
A simple package to be built with the uv build backend.
"###);
let record_file = metadata_dir
.path()
.join("uv_backend-0.1.0.dist-info/RECORD");
assert_snapshot!(fs_err::read_to_string(record_file).unwrap(), @r###"
uv_backend-0.1.0.dist-info/WHEEL,sha256=70ce44709b6a53e0d0c5a6755b0290179697020f1f867e794f26154fe4825738,79
uv_backend-0.1.0.dist-info/METADATA,sha256=e4a0d390317d7182f65ea978254c71ed283e0a4242150cf1c99a694b113ff68d,224
uv_backend-0.1.0.dist-info/RECORD,,
"###);
let wheel_file = metadata_dir.path().join("uv_backend-0.1.0.dist-info/WHEEL");
let filters = vec![(uv_version::version(), "[VERSION]")];
with_settings!({
filters => filters
}, {
assert_snapshot!(fs_err::read_to_string(wheel_file).unwrap(), @r###"
Wheel-Version: 1.0
Generator: uv [VERSION]
Root-Is-Purelib: true
Tag: py3-none-any
"###);
});
}