diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..b22b8ee4a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.stderr linguist-generated +*.snap linguist-generated diff --git a/Cargo.lock b/Cargo.lock index b0a72b6aa..3612a8a51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,6 +393,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -522,6 +532,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -802,6 +824,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1059,6 +1087,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + [[package]] name = "group" version = "0.13.0" @@ -1411,6 +1452,20 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "insta" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +dependencies = [ + "console", + "globset", + "lazy_static", + "linked-hash-map", + "similar", + "walkdir", +] + [[package]] name = "instant" version = "0.1.13" @@ -1698,6 +1753,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2572,6 +2633,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.24" @@ -2818,6 +2888,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "slab" version = "0.4.9" @@ -3008,17 +3084,21 @@ version = "0.3.0" dependencies = [ "convert_case", "darling", + "insta", "itertools 0.13.0", "k8s-openapi", "k8s-version", "kube", + "prettyplease", "proc-macro2", "quote", + "regex", "rstest", "schemars", "serde", "serde_json", "serde_yaml", + "snafu 0.8.4", "strum", "syn 2.0.77", "trybuild", @@ -3670,6 +3750,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index b3e8c2e14..46b6941a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ either = "1.13.0" futures = "0.3.30" futures-util = "0.3.30" indexmap = "2.5" +insta = { version= "1.40", features = ["glob"] } hyper = { version = "1.4.1", features = ["full"] } hyper-util = "0.1.8" itertools = "0.13.0" @@ -41,6 +42,7 @@ opentelemetry-otlp = "0.16.0" opentelemetry-semantic-conventions = "0.15.0" p256 = { version = "0.13.2", features = ["ecdsa"] } pin-project = "1.1.5" +prettyplease = "0.2.22" proc-macro2 = "1.0.86" quote = "1.0.37" rand = "0.8.5" @@ -77,7 +79,12 @@ x509-cert = { version = "0.2.5", features = ["builder"] } zeroize = "1.8.1" # Use O3 in tests to improve the RSA key generation speed in the stackable-certs crate -[profile.test.package.stackable-certs] -opt-level = 3 -[profile.test.package."rsa"] -opt-level = 3 +[profile.test.package] +stackable-certs.opt-level = 3 +rsa.opt-level = 3 + +# Run snapshot testing faster even as a dev dependecy. +# See https://insta.rs/docs/quickstart/#optional-faster-runs +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index d53750ae1..1329633ce 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -38,9 +38,13 @@ syn.workspace = true quote.workspace = true [dev-dependencies] +insta.workspace = true +prettyplease.workspace = true +regex.workspace = true rstest.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true +snafu.workspace = true trybuild.workspace = true diff --git a/crates/stackable-versioned-macros/fixtures/README.md b/crates/stackable-versioned-macros/fixtures/README.md new file mode 100644 index 000000000..f27b580ea --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/README.md @@ -0,0 +1,60 @@ +# Snapshot Testing + +> [!NOTE] +> Also see the compile-fail tests, described [here](../tests/README.md). + +This folder contains fixtures for snapshot testing the `#[versioned()]` macro. Snapshot testing is +done using the [insta] crate. It provides a [CLI tool][insta-cli] called `cargo-insta` and a +[VS Code extension][insta-ext]. + +Test inputs and snapshots of the expected output are located in the `fixtures` folder. There are two +inputs to the `#[versioned()]` macro because it is an attribute macro: + +> The first TokenStream is the delimited token tree following the attribute’s name, not including +> the outer delimiters. If the attribute is written as a bare attribute name, the attribute +> TokenStream is empty. The second TokenStream is the rest of the item including other attributes on +> the item. +> +> _(Taken from the [Rust reference][rust-ref])_ + +Because of that, a special delimiter is used in the input files which separates the two inputs while +still enabling developers to write valid Rust code. The delimiter is `// ---\n`. Most of the inner +workings are located in [this file](../src/test_utils.rs). + +```rust +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1") +)] +// --- <- See here! +pub(crate) struct Foo { + #[versioned( + changed(since = "v1beta1", from_name = "jjj", from_type = "u8"), + changed(since = "v1", from_type = "u16"), + )] + bar: usize, + baz: bool, +} +``` + +## Recommended Workflow + +First, add new input files (which automatically get picked up by `insta`) to the `fixtures/inputs` +folder. Make sure the delimiter is placed correctly between the attribute and the container +definition. Doc comments on the container have to be placed after the delimiter. Next, generate the +snapshot files (initially not accepted) by running + +```shell +cargo insta test -p stackable-versioned-macros +``` + +This command will place the new snapshot files (with a `.new` extension) in the `fixtures/snapshots` +folder. These new snapshot files must not appear on `main`, but can be shared on branches for +collaboration. To review them, run the `cargo insta review` command, then accept or fix the +snapshots. Once all are accepted (ie: no `.new` files remaining), check in the files. + +[rust-ref]: https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros +[insta-ext]: https://insta.rs/docs/vscode/ +[insta-cli]: https://insta.rs/docs/cli/ +[insta]: https://insta.rs/ diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_enum.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_enum.rs new file mode 100644 index 000000000..f3b78aa75 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_enum.rs @@ -0,0 +1,38 @@ +#[versioned( + version(name = "v1alpha1"), + version( + name = "v1beta1", + doc = r#" + Additional docs for this version which are purposefully long to + show how manual line wrapping works. \ + Multi-line docs are also supported, as per regular doc-comments. + "# + ), + version(name = "v1beta2"), + version(name = "v1"), + version(name = "v2"), + options(skip(from)) +)] +// --- +#[derive(Default)] +enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + + /// Keep the main field docs the same, even after the field is + /// deprecated. + #[versioned(deprecated(since = "v1beta1", note = "gone"))] + DeprecatedBar, + + /// This is for baz + #[versioned(added(since = "v1beta1"))] + // Just to check stackable-versioned deprecation warning appears. + // #[deprecated] + Baz, + + /// This is will keep changing over time. + #[versioned(changed(since = "v1beta1", from_name = "Qoox"))] + #[versioned(changed(since = "v1", from_name = "Qaax"))] + Quux, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_struct.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_struct.rs new file mode 100644 index 000000000..649477dcb --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/attribute_struct.rs @@ -0,0 +1,35 @@ +#[versioned( + version(name = "v1alpha1"), + version( + name = "v1beta1", + doc = r#" + Additional docs for this version which are purposefully long to + show how manual line wrapping works. \ + Multi-line docs are also supported, as per regular doc-comments. + "# + ), + version(name = "v1beta2"), + version(name = "v1"), + version(name = "v2"), + options(skip(from)) +)] +// --- +/// Test +#[derive(Default)] +struct Foo { + /// This field is available in every version (so far). + foo: String, + + /// Keep the main field docs the same, even after the field is deprecated. + #[versioned(deprecated(since = "v1beta1", note = "gone"))] + deprecated_bar: String, + + /// This is for baz + #[versioned(added(since = "v1beta1"))] + baz: String, + + /// This is will keep changing over time. + #[versioned(changed(since = "v1beta1", from_name = "qoox"))] + #[versioned(changed(since = "v1", from_name = "qaax"))] + quux: String, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/basic_struct.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/basic_struct.rs new file mode 100644 index 000000000..12ab3e260 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/basic_struct.rs @@ -0,0 +1,18 @@ +#[versioned( + version(name = "v1alpha1", deprecated), + version(name = "v1beta1"), + version(name = "v1"), + version(name = "v2"), + version(name = "v3") +)] +// --- +pub(crate) struct Foo { + #[versioned( + changed(since = "v1beta1", from_name = "jjj", from_type = "u8"), + changed(since = "v1", from_type = "u16"), + deprecated(since = "v2", note = "not empty") + )] + /// Test + deprecated_bar: usize, + baz: bool, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_enum.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_enum.rs new file mode 100644 index 000000000..af942c1d4 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_enum.rs @@ -0,0 +1,13 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + version(name = "v2"), + version(name = "v3") +)] +// --- +enum Foo { + #[versioned(deprecated(since = "v1"))] + DeprecatedBar, + Baz, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_struct.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_struct.rs new file mode 100644 index 000000000..b20c25bbd --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_struct.rs @@ -0,0 +1,13 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + version(name = "v2"), + version(name = "v3") +)] +// --- +struct Foo { + #[versioned(deprecated(since = "v1", note = "gone"))] + deprecated_bar: usize, + baz: bool, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/rename.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/rename.rs new file mode 100644 index 000000000..4cfb1f0a4 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/rename.rs @@ -0,0 +1,11 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1") +)] +// --- +struct Foo { + #[versioned(changed(since = "v1beta1", from_name = "bat"))] + bar: usize, + baz: bool, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/skip_from_field.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/skip_from_field.rs new file mode 100644 index 000000000..bb7c6dfe0 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/skip_from_field.rs @@ -0,0 +1,14 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1", skip(from)), + version(name = "v1") +)] +// --- +pub struct Foo { + #[versioned( + added(since = "v1beta1"), + deprecated(since = "v1", note = "not needed") + )] + deprecated_bar: usize, + baz: bool, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/k8s/basic.rs b/crates/stackable-versioned-macros/fixtures/inputs/k8s/basic.rs new file mode 100644 index 000000000..4e4449a0c --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/k8s/basic.rs @@ -0,0 +1,21 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + k8s( + group = "stackable.tech", + singular = "foo", + plural = "foos", + namespaced, + ) +)] +// --- +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] +pub struct FooSpec { + #[versioned( + added(since = "v1beta1"), + changed(since = "v1", from_name = "bah", from_type = "u16") + )] + bar: usize, + baz: bool, +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_enum.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_enum.rs.snap new file mode 100644 index 000000000..32f2162a9 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_enum.rs.snap @@ -0,0 +1,96 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/attribute_enum.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + #[derive(Default)] + pub enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + /// Keep the main field docs the same, even after the field is + /// deprecated. + Bar, + /// This is will keep changing over time. + Qoox, + } +} +#[automatically_derived] +mod v1beta1 { + use super::*; + /// + ///Additional docs for this version which are purposefully long to + ///show how manual line wrapping works. \ + ///Multi-line docs are also supported, as per regular doc-comments. + #[derive(Default)] + pub enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + /// Keep the main field docs the same, even after the field is + /// deprecated. + #[deprecated = "gone"] + DeprecatedBar, + /// This is for baz + Baz, + /// This is will keep changing over time. + Qaax, + } +} +#[automatically_derived] +mod v1beta2 { + use super::*; + #[derive(Default)] + pub enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + /// Keep the main field docs the same, even after the field is + /// deprecated. + #[deprecated] + DeprecatedBar, + /// This is for baz + Baz, + /// This is will keep changing over time. + Qaax, + } +} +#[automatically_derived] +mod v1 { + use super::*; + #[derive(Default)] + pub enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + /// Keep the main field docs the same, even after the field is + /// deprecated. + #[deprecated] + DeprecatedBar, + /// This is for baz + Baz, + /// This is will keep changing over time. + Quux, + } +} +#[automatically_derived] +mod v2 { + use super::*; + #[derive(Default)] + pub enum Foo { + /// This variant is available in every version (so far). + #[default] + Foo, + /// Keep the main field docs the same, even after the field is + /// deprecated. + #[deprecated] + DeprecatedBar, + /// This is for baz + Baz, + /// This is will keep changing over time. + Quux, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_struct.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_struct.rs.snap new file mode 100644 index 000000000..440311ca7 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@attribute_struct.rs.snap @@ -0,0 +1,91 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/attribute_struct.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + /// Test + #[derive(Default)] + pub struct Foo { + /// This field is available in every version (so far). + pub foo: String, + /// Keep the main field docs the same, even after the field is deprecated. + pub bar: String, + /// This is will keep changing over time. + pub qoox: String, + } +} +#[automatically_derived] +mod v1beta1 { + use super::*; + /// + ///Additional docs for this version which are purposefully long to + ///show how manual line wrapping works. \ + ///Multi-line docs are also supported, as per regular doc-comments. + /// Test + #[derive(Default)] + pub struct Foo { + /// This field is available in every version (so far). + pub foo: String, + /// Keep the main field docs the same, even after the field is deprecated. + #[deprecated = "gone"] + pub deprecated_bar: String, + /// This is for baz + pub baz: String, + /// This is will keep changing over time. + pub qaax: String, + } +} +#[automatically_derived] +mod v1beta2 { + use super::*; + /// Test + #[derive(Default)] + pub struct Foo { + /// This field is available in every version (so far). + pub foo: String, + /// Keep the main field docs the same, even after the field is deprecated. + #[deprecated] + pub deprecated_bar: String, + /// This is for baz + pub baz: String, + /// This is will keep changing over time. + pub qaax: String, + } +} +#[automatically_derived] +mod v1 { + use super::*; + /// Test + #[derive(Default)] + pub struct Foo { + /// This field is available in every version (so far). + pub foo: String, + /// Keep the main field docs the same, even after the field is deprecated. + #[deprecated] + pub deprecated_bar: String, + /// This is for baz + pub baz: String, + /// This is will keep changing over time. + pub quux: String, + } +} +#[automatically_derived] +mod v2 { + use super::*; + /// Test + #[derive(Default)] + pub struct Foo { + /// This field is available in every version (so far). + pub foo: String, + /// Keep the main field docs the same, even after the field is deprecated. + #[deprecated] + pub deprecated_bar: String, + /// This is for baz + pub baz: String, + /// This is will keep changing over time. + pub quux: String, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@basic_struct.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@basic_struct.rs.snap new file mode 100644 index 000000000..1de8b2805 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@basic_struct.rs.snap @@ -0,0 +1,92 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/basic_struct.rs +--- +#[automatically_derived] +#[deprecated = "Version v1alpha1 is deprecated"] +pub(crate) mod v1alpha1 { + use super::*; + pub struct Foo { + /// Test + pub jjj: u8, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v1beta1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: __sv_foo.jjj.into(), + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +pub(crate) mod v1beta1 { + use super::*; + pub struct Foo { + /// Test + pub bar: u16, + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +pub(crate) mod v1 { + use super::*; + pub struct Foo { + /// Test + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v2::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +pub(crate) mod v2 { + use super::*; + pub struct Foo { + /// Test + #[deprecated = "not empty"] + pub deprecated_bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v3::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.deprecated_bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +pub(crate) mod v3 { + use super::*; + pub struct Foo { + /// Test + #[deprecated] + pub deprecated_bar: usize, + pub baz: bool, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_enum.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_enum.rs.snap new file mode 100644 index 000000000..17503b80c --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_enum.rs.snap @@ -0,0 +1,87 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_enum.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + pub enum Foo { + Bar, + Baz, + } +} +#[automatically_derived] +impl From for v1beta1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + match __sv_foo { + v1alpha1::Foo::Bar => v1beta1::Foo::Bar, + v1alpha1::Foo::Baz => v1beta1::Foo::Baz, + } + } +} +#[automatically_derived] +mod v1beta1 { + use super::*; + pub enum Foo { + Bar, + Baz, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + match __sv_foo { + v1beta1::Foo::Bar => v1::Foo::DeprecatedBar, + v1beta1::Foo::Baz => v1::Foo::Baz, + } + } +} +#[automatically_derived] +mod v1 { + use super::*; + pub enum Foo { + #[deprecated] + DeprecatedBar, + Baz, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v2::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + match __sv_foo { + v1::Foo::DeprecatedBar => v2::Foo::DeprecatedBar, + v1::Foo::Baz => v2::Foo::Baz, + } + } +} +#[automatically_derived] +mod v2 { + use super::*; + pub enum Foo { + #[deprecated] + DeprecatedBar, + Baz, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v3::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + match __sv_foo { + v2::Foo::DeprecatedBar => v3::Foo::DeprecatedBar, + v2::Foo::Baz => v3::Foo::Baz, + } + } +} +#[automatically_derived] +mod v3 { + use super::*; + pub enum Foo { + #[deprecated] + DeprecatedBar, + Baz, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_struct.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_struct.rs.snap new file mode 100644 index 000000000..afb5354a1 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@deprecate_struct.rs.snap @@ -0,0 +1,87 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/deprecate_struct.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + pub struct Foo { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1beta1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v1beta1 { + use super::*; + pub struct Foo { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v1 { + use super::*; + pub struct Foo { + #[deprecated = "gone"] + pub deprecated_bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v2::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.deprecated_bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v2 { + use super::*; + pub struct Foo { + #[deprecated] + pub deprecated_bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +#[allow(deprecated)] +impl From for v3::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.deprecated_bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v3 { + use super::*; + pub struct Foo { + #[deprecated] + pub deprecated_bar: usize, + pub baz: bool, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@rename.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@rename.rs.snap new file mode 100644 index 000000000..6e1bc3e84 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@rename.rs.snap @@ -0,0 +1,47 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/rename.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + pub struct Foo { + pub bat: usize, + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1beta1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bat, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v1beta1 { + use super::*; + pub struct Foo { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + bar: __sv_foo.bar, + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +mod v1 { + use super::*; + pub struct Foo { + pub bar: usize, + pub baz: bool, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@skip_from_field.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@skip_from_field.rs.snap new file mode 100644 index 000000000..1b40c6704 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@skip_from_field.rs.snap @@ -0,0 +1,38 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/skip_from_field.rs +--- +#[automatically_derived] +pub mod v1alpha1 { + use super::*; + pub struct Foo { + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1beta1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: ::std::default::Default::default(), + baz: __sv_foo.baz, + } + } +} +#[automatically_derived] +pub mod v1beta1 { + use super::*; + pub struct Foo { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +pub mod v1 { + use super::*; + pub struct Foo { + #[deprecated = "not needed"] + pub deprecated_bar: usize, + pub baz: bool, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots.snap new file mode 100644 index 000000000..6561d0db3 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots.snap @@ -0,0 +1,115 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/k8s/basic.rs +--- +#[automatically_derived] +pub mod v1alpha1 { + use super::*; + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] + #[derive(::kube::CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1alpha1", + kind = "Foo", + singular = "foo", + plural = "foos", + namespaced + )] + pub struct FooSpec { + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1beta1::FooSpec { + fn from(__sv_foospec: v1alpha1::FooSpec) -> Self { + Self { + bah: ::std::default::Default::default(), + baz: __sv_foospec.baz, + } + } +} +#[automatically_derived] +pub mod v1beta1 { + use super::*; + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] + #[derive(::kube::CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1beta1", + kind = "Foo", + singular = "foo", + plural = "foos", + namespaced + )] + pub struct FooSpec { + pub bah: u16, + pub baz: bool, + } +} +#[automatically_derived] +impl From for v1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bah.into(), + baz: __sv_foospec.baz, + } + } +} +#[automatically_derived] +pub mod v1 { + use super::*; + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] + #[derive(::kube::CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1", + kind = "Foo", + singular = "foo", + plural = "foos", + namespaced + )] + pub struct FooSpec { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +pub enum Foo { + V1Alpha1, + V1Beta1, + V1, +} +#[automatically_derived] +impl ::std::fmt::Display for Foo { + fn fmt( + &self, + f: &mut ::std::fmt::Formatter<'_>, + ) -> ::std::result::Result<(), ::std::fmt::Error> { + match self { + Foo::V1Alpha1 => f.write_str("v1alpha1"), + Foo::V1Beta1 => f.write_str("v1beta1"), + Foo::V1 => f.write_str("v1"), + } + } +} +#[automatically_derived] +impl Foo { + /// Generates a merged CRD which contains all versions defined using the + /// `#[versioned()]` macro. + pub fn merged_crd( + stored_apiversion: Self, + ) -> ::std::result::Result< + ::k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, + ::kube::core::crd::MergeError, + > { + ::kube::core::crd::merge_crds( + vec![ + < v1alpha1::Foo as ::kube::CustomResourceExt > ::crd(), < v1beta1::Foo as + ::kube::CustomResourceExt > ::crd(), < v1::Foo as + ::kube::CustomResourceExt > ::crd() + ], + &stored_apiversion.to_string(), + ) + } +} diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index d5ec7dea9..469adae82 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -4,6 +4,9 @@ use syn::{DeriveInput, Error}; use crate::attrs::common::ContainerAttributes; +#[cfg(test)] +mod test_utils; + mod attrs; mod codegen; mod consts; @@ -473,14 +476,6 @@ println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); /// a cluster scoped. #[proc_macro_attribute] pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { - let attrs = match NestedMeta::parse_meta_list(attrs.into()) { - Ok(attrs) => match ContainerAttributes::from_list(&attrs) { - Ok(attrs) => attrs, - Err(err) => return err.write_errors().into(), - }, - Err(err) => return darling::Error::from(err).write_errors().into(), - }; - // NOTE (@Techassi): For now, we can just use the DeriveInput type here, // because we only support structs end enums to be versioned. // In the future - if we decide to support modules - this requires @@ -488,8 +483,49 @@ pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { // use an enum with two variants: Container(DeriveInput) and // Module(ItemMod). let input = syn::parse_macro_input!(input as DeriveInput); + versioned_impl(attrs.into(), input).into() +} + +fn versioned_impl(attrs: proc_macro2::TokenStream, input: DeriveInput) -> proc_macro2::TokenStream { + let attrs = match NestedMeta::parse_meta_list(attrs) { + Ok(attrs) => match ContainerAttributes::from_list(&attrs) { + Ok(attrs) => attrs, + Err(err) => return err.write_errors(), + }, + Err(err) => return darling::Error::from(err).write_errors(), + }; + + codegen::expand(attrs, input).unwrap_or_else(Error::into_compile_error) +} + +#[cfg(test)] +mod test { + use insta::{assert_snapshot, glob}; + + use super::*; + + #[test] + fn default_snapshots() { + let _settings_guard = test_utils::set_snapshot_path().bind_to_scope(); + + glob!("../fixtures/inputs/default", "*.rs", |path| { + let formatted = test_utils::expand_from_file(path) + .inspect_err(|err| eprintln!("{err}")) + .unwrap(); + assert_snapshot!(formatted); + }); + } + + #[cfg(feature = "k8s")] + #[test] + fn k8s_snapshots() { + let _settings_guard = test_utils::set_snapshot_path().bind_to_scope(); - codegen::expand(attrs, input) - .unwrap_or_else(Error::into_compile_error) - .into() + glob!("../fixtures/inputs/k8s", "*.rs", |path| { + let formatted = test_utils::expand_from_file(path) + .inspect_err(|err| eprintln!("{err}")) + .unwrap(); + assert_snapshot!(formatted); + }); + } } diff --git a/crates/stackable-versioned-macros/src/test_utils.rs b/crates/stackable-versioned-macros/src/test_utils.rs new file mode 100644 index 000000000..92dbca906 --- /dev/null +++ b/crates/stackable-versioned-macros/src/test_utils.rs @@ -0,0 +1,76 @@ +use std::{ + path::{Path, PathBuf}, + str::FromStr, + sync::LazyLock, +}; + +use insta::Settings; +use proc_macro2::TokenStream; +use regex::Regex; +use snafu::{OptionExt, ResultExt, Snafu}; +use syn::DeriveInput; + +use crate::versioned_impl; + +const DELIMITER: &str = "// ---\n"; + +static REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"#\[versioned\(\n(?P[[:ascii:]]+)\n\)\]") + .expect("failed to compile versioned regex") +}); + +#[derive(Debug, Snafu)] +pub(crate) enum Error { + #[snafu(display("failed to read input file"))] + ReadFile { source: std::io::Error }, + + #[snafu(display("failed to find delimiter"))] + MissingDelimiter, + + #[snafu(display("failed to find regex match group"))] + MissingRegexMatchGroup, + + #[snafu(display("failed to parse token stream"))] + ParseTokenStream { source: proc_macro2::LexError }, + + #[snafu(display("failed to parse derive input"))] + ParseDeriveInput { source: syn::Error }, + + #[snafu(display("failed to parse output file"))] + ParseOutputFile { source: syn::Error }, +} + +pub(crate) fn expand_from_file(path: &Path) -> Result { + let input = std::fs::read_to_string(path).context(ReadFileSnafu)?; + let (attrs, input) = prepare_from_string(input)?; + + let expanded = versioned_impl(attrs, input).to_string(); + let parsed = syn::parse_file(&expanded).context(ParseOutputFileSnafu)?; + + Ok(prettyplease::unparse(&parsed)) +} + +fn prepare_from_string(input: String) -> Result<(TokenStream, DeriveInput), Error> { + let (attrs, input) = input.split_once(DELIMITER).context(MissingDelimiterSnafu)?; + + let attrs = REGEX + .captures(attrs) + .unwrap() + .name("args") + .context(MissingRegexMatchGroupSnafu)? + .as_str(); + + let attrs = TokenStream::from_str(attrs).context(ParseTokenStreamSnafu)?; + let input = TokenStream::from_str(input).context(ParseTokenStreamSnafu)?; + let input = syn::parse2(input).context(ParseDeriveInputSnafu)?; + + Ok((attrs, input)) +} + +pub(crate) fn set_snapshot_path() -> Settings { + let dir = std::env::var("CARGO_MANIFEST_DIR").expect("env var CARGO_MANIFEST_DIR must be set"); + let mut settings = Settings::clone_current(); + settings.set_snapshot_path(PathBuf::from(dir).join("fixtures/snapshots")); + + settings +} diff --git a/crates/stackable-versioned-macros/tests/README.md b/crates/stackable-versioned-macros/tests/README.md new file mode 100644 index 000000000..a8d3f7598 --- /dev/null +++ b/crates/stackable-versioned-macros/tests/README.md @@ -0,0 +1,17 @@ +# Compile-Fail Testing + +> [!NOTE] +> Also see the snapshot tests, described [here](../fixtures/README.md). + +This type of testing is part of UI testing. These tests assert two things: First, the code should +**not** compile and secondly should also produce the expected rustc (compiler) error message. For +this type of testing, we use the [`trybuild`][trybuild] crate. + +Tests are currently separated into two folders: `default` and `k8s`. The default test cases don't +require any additional features to be activated. The Kubernetes specific tests require the `k8s` +feature to be enabled. These tests can be run with `cargo test --all-features`. + +Further information about the workflow are described [here][workflow]. + +[workflow]: https://docs.rs/trybuild/latest/trybuild/#workflow +[trybuild]: https://docs.rs/trybuild/latest/trybuild/ diff --git a/crates/stackable-versioned-macros/tests/default/fail/README.md b/crates/stackable-versioned-macros/tests/default/fail/README.md deleted file mode 100644 index d19327a52..000000000 --- a/crates/stackable-versioned-macros/tests/default/fail/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# trybuild bad cases - -Code that is expected to fail lives here along with the expected compiler output -for each case. Please see the docs in [tests/trybuild.rs]. - -[tests/trybuild.rs]: ../trybuild.rs diff --git a/crates/stackable-versioned-macros/tests/default/pass/README.md b/crates/stackable-versioned-macros/tests/default/pass/README.md deleted file mode 100644 index e928d0edd..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# trybuild good cases - -Code that is expected to compile lives here. Please see the docs in -[tests/trybuild.rs]. - -[tests/trybuild.rs]: ../trybuild.rs diff --git a/crates/stackable-versioned-macros/tests/default/pass/attributes_enum.rs b/crates/stackable-versioned-macros/tests/default/pass/attributes_enum.rs deleted file mode 100644 index ae73085dd..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/attributes_enum.rs +++ /dev/null @@ -1,43 +0,0 @@ -use stackable_versioned_macros::versioned; - -#[allow(dead_code)] -fn main() { - /// General enum docs that cover all versions. - #[versioned( - version(name = "v1alpha1"), - version( - name = "v1beta1", - doc = r#" - Additional docs for this version which are purposefully long to - show how manual line wrapping works. \ - Multi-line docs are also supported, as per regular doc-comments. - "# - ), - version(name = "v1beta2"), - version(name = "v1"), - version(name = "v2"), - options(skip(from)) - )] - #[derive(Default)] - enum Foo { - /// This variant is available in every version (so far). - #[default] - Foo, - - /// Keep the main field docs the same, even after the field is - /// deprecated. - #[versioned(deprecated(since = "v1beta1", note = "gone"))] - DeprecatedBar, - - /// This is for baz - #[versioned(added(since = "v1beta1"))] - // Just to check stackable-versioned deprecation warning appears. - // #[deprecated] - Baz, - - /// This is will keep changing over time. - #[versioned(changed(since = "v1beta1", from_name = "Qoox"))] - #[versioned(changed(since = "v1", from_name = "Qaax"))] - Quux, - } -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/attributes_struct.rs b/crates/stackable-versioned-macros/tests/default/pass/attributes_struct.rs deleted file mode 100644 index 7790d424e..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/attributes_struct.rs +++ /dev/null @@ -1,60 +0,0 @@ -use stackable_versioned_macros::versioned; - -fn main() { - /// General struct docs that cover all versions. - #[versioned( - version(name = "v1alpha1"), - version( - name = "v1beta1", - doc = r#" - Additional docs for this version which are purposefully long to - show how manual line wrapping works. \ - Multi-line docs are also supported, as per regular doc-comments. - "# - ), - version(name = "v1beta2"), - version(name = "v1"), - version(name = "v2"), - options(skip(from)) - )] - #[derive(Default)] - struct Foo { - /// This field is available in every version (so far). - foo: String, - - /// Keep the main field docs the same, even after the field is deprecated. - #[versioned(deprecated(since = "v1beta1", note = "gone"))] - deprecated_bar: String, - - /// This is for baz - #[versioned(added(since = "v1beta1"))] - baz: String, - - /// This is will keep changing over time. - #[versioned(changed(since = "v1beta1", from_name = "qoox"))] - #[versioned(changed(since = "v1", from_name = "qaax"))] - quux: String, - } - - let _ = v1alpha1::Foo { - foo: String::from("foo"), - bar: String::from("Hello"), - qoox: String::from("world"), - }; - - #[allow(deprecated)] - let _ = v1beta1::Foo { - foo: String::from("foo"), - deprecated_bar: String::from("Hello"), - baz: String::from("Hello"), - qaax: String::from("World"), - }; - - #[allow(deprecated)] - let _ = v1::Foo { - foo: String::from("foo"), - deprecated_bar: String::from("Hello"), - baz: String::from("Hello"), - quux: String::from("World"), - }; -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/basic.rs b/crates/stackable-versioned-macros/tests/default/pass/basic.rs deleted file mode 100644 index ea86f8814..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/basic.rs +++ /dev/null @@ -1,43 +0,0 @@ -use stackable_versioned_macros::versioned; - -// To expand the generated code (for debugging and testing), it is recommended -// to first change directory via `cd crates/stackable-versioned` and to then -// run `cargo expand --test basic --all-features`. -#[allow(dead_code)] -#[versioned( - version(name = "v1alpha1", deprecated), - version(name = "v1beta1"), - version(name = "v1"), - version(name = "v2"), - version(name = "v3") -)] -pub(crate) struct Foo { - #[versioned( - changed(since = "v1beta1", from_name = "jjj", from_type = "u8"), - changed(since = "v1", from_type = "u16"), - deprecated(since = "v2", note = "not empty") - )] - /// Test - deprecated_bar: usize, - baz: bool, -} - -fn main() { - #[allow(deprecated)] - let _ = v1alpha1::Foo { jjj: 0, baz: false }; - let _ = v1beta1::Foo { bar: 0, baz: false }; - let _ = v1::Foo { bar: 0, baz: false }; - - #[allow(deprecated)] - let _ = v2::Foo { - deprecated_bar: 0, - baz: false, - }; - - // The latest version (v3) - #[allow(deprecated)] - let _ = v3::Foo { - deprecated_bar: 0, - baz: false, - }; -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/deprecate_enum.rs b/crates/stackable-versioned-macros/tests/default/pass/deprecate_enum.rs deleted file mode 100644 index b19f36b88..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/deprecate_enum.rs +++ /dev/null @@ -1,16 +0,0 @@ -use stackable_versioned_macros::versioned; - -fn main() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1"), - version(name = "v2"), - version(name = "v3") - )] - enum Foo { - #[versioned(deprecated(since = "v1"))] - DeprecatedBar, - Baz, - } -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/deprecate_struct.rs b/crates/stackable-versioned-macros/tests/default/pass/deprecate_struct.rs deleted file mode 100644 index feeafd377..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/deprecate_struct.rs +++ /dev/null @@ -1,16 +0,0 @@ -use stackable_versioned_macros::versioned; - -fn main() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1"), - version(name = "v2"), - version(name = "v3") - )] - struct Foo { - #[versioned(deprecated(since = "v1", note = "gone"))] - deprecated_bar: usize, - baz: bool, - } -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/rename.rs b/crates/stackable-versioned-macros/tests/default/pass/rename.rs deleted file mode 100644 index dac9b7689..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/rename.rs +++ /dev/null @@ -1,14 +0,0 @@ -use stackable_versioned_macros::versioned; - -fn main() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1") - )] - struct Foo { - #[versioned(changed(since = "v1beta1", from_name = "bat"))] - bar: usize, - baz: bool, - } -} diff --git a/crates/stackable-versioned-macros/tests/default/pass/skip_from_version.rs b/crates/stackable-versioned-macros/tests/default/pass/skip_from_version.rs deleted file mode 100644 index 3308dc23b..000000000 --- a/crates/stackable-versioned-macros/tests/default/pass/skip_from_version.rs +++ /dev/null @@ -1,28 +0,0 @@ -use stackable_versioned_macros::versioned; - -#[allow(unused_variables)] -fn main() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1", skip(from)), - version(name = "v1") - )] - pub struct Foo { - #[versioned( - added(since = "v1beta1"), - deprecated(since = "v1", note = "not needed") - )] - deprecated_bar: usize, - baz: bool, - } - - let foo_v1alpha1 = v1alpha1::Foo { baz: true }; - let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1); - - #[allow(deprecated)] - // v1beta1 has no From impl, so we convert it manually - let foo_v1 = v1::Foo { - deprecated_bar: 0, - baz: true, - }; -} diff --git a/crates/stackable-versioned-macros/tests/enum.rs b/crates/stackable-versioned-macros/tests/enum.rs deleted file mode 100644 index c43b1da1e..000000000 --- a/crates/stackable-versioned-macros/tests/enum.rs +++ /dev/null @@ -1,23 +0,0 @@ -use stackable_versioned_macros::versioned; - -#[test] -fn versioned_enum() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1", deprecated), - version(name = "v1") - )] - pub enum Foo { - #[versioned(added(since = "v1beta1"), deprecated(since = "v1", note = "bye"))] - DeprecatedBar, - Baz, - } - - let v1alpha1_foo = v1alpha1::Foo::Baz; - #[allow(deprecated)] - let v1beta1_foo = v1beta1::Foo::from(v1alpha1_foo); - let v1_foo = v1::Foo::from(v1beta1_foo); - - // TODO (@Techassi): Forward derive PartialEq - assert!(matches!(v1_foo, v1::Foo::Baz)) -} diff --git a/crates/stackable-versioned-macros/tests/from.rs b/crates/stackable-versioned-macros/tests/from.rs deleted file mode 100644 index 0012743c1..000000000 --- a/crates/stackable-versioned-macros/tests/from.rs +++ /dev/null @@ -1,53 +0,0 @@ -use stackable_versioned_macros::versioned; - -#[allow(deprecated)] -#[test] -fn from() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1") - )] - pub struct Foo { - #[versioned( - added(since = "v1beta1"), - deprecated(since = "v1", note = "not needed") - )] - deprecated_bar: usize, - baz: bool, - } - - let foo_v1alpha1 = v1alpha1::Foo { baz: true }; - let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1); - let foo_v1 = v1::Foo::from(foo_v1beta1); - - assert_eq!(foo_v1.deprecated_bar, 0); - assert!(foo_v1.baz); -} - -#[test] -fn from_custom_default_fn() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1") - )] - pub struct Foo { - #[versioned( - added(since = "v1beta1", default = "default_bar"), - deprecated(since = "v1", note = "not needed") - )] - deprecated_bar: usize, - baz: bool, - } - - fn default_bar() -> usize { - 42 - } - - let foo_v1alpha1 = v1alpha1::Foo { baz: true }; - let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1); - - assert_eq!(foo_v1beta1.bar, 42); - assert!(foo_v1beta1.baz); -} diff --git a/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs b/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs deleted file mode 100644 index 158630f19..000000000 --- a/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs +++ /dev/null @@ -1,31 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use stackable_versioned_macros::versioned; - -#[allow(deprecated)] -fn main() { - #[versioned( - version(name = "v1alpha1"), - version(name = "v1beta1"), - version(name = "v1"), - k8s( - group = "stackable.tech", - singular = "foo", - plural = "foos", - namespaced, - ) - )] - #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] - pub struct FooSpec { - #[versioned( - added(since = "v1beta1"), - changed(since = "v1", from_name = "bah", from_type = "u16") - )] - bar: usize, - baz: bool, - } - - let merged_crd = Foo::merged_crd(Foo::V1).unwrap(); - println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); -} diff --git a/crates/stackable-versioned-macros/tests/trybuild.rs b/crates/stackable-versioned-macros/tests/trybuild.rs index fda62dee9..3070189c9 100644 --- a/crates/stackable-versioned-macros/tests/trybuild.rs +++ b/crates/stackable-versioned-macros/tests/trybuild.rs @@ -16,17 +16,6 @@ // again but before running tests, otherwise compilation will fail (as expected). #[allow(dead_code)] mod default { - // mod pass { - // // mod attributes_enum; - // // mod attributes_struct; - // // mod basic; - - // // mod deprecate_enum; - // // mod deprecate_struct; - // // mod rename; - // // mod skip_from_version; - // } - // mod fail { // mod deprecate; // mod skip_from_all; @@ -37,17 +26,12 @@ mod default { #[test] fn default_macros() { let t = trybuild::TestCases::new(); - t.pass("tests/default/pass/*.rs"); t.compile_fail("tests/default/fail/*.rs"); } #[cfg(feature = "k8s")] #[allow(dead_code)] mod k8s { - // mod pass { - // mod crd; - // } - // mod fail { // mod crd; // } @@ -57,6 +41,5 @@ mod k8s { #[test] fn k8s_macros() { let t = trybuild::TestCases::new(); - t.pass("tests/k8s/pass/*.rs"); t.compile_fail("tests/k8s/fail/*.rs"); } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index dbe7d9d11..2158638b3 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add snapshot tests to verify generated code matches expected output ([#881]). + +[#881]: https://github.com/stackabletech/operator-rs/pull/881 + ## [0.3.0] - 2024-09-26 ### Added