Skip to content

Commit bdcd118

Browse files
sbernauerrazvan
andauthored
feat(stackable-versioned)!: Automatically generate conversion roundtrip tests (#1172)
* feat(stackable-versioned)!: Automatically generate conversion roundtrip tests * changelog * Improve docs * Remove debug statement * Remove unused dependency * Apply suggestions from code review Co-authored-by: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> * implement RoundtripTestData for ScalerSpec --------- Co-authored-by: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com>
1 parent 96f4257 commit bdcd118

25 files changed

Lines changed: 756 additions & 2 deletions

crates/stackable-operator/src/crd/authentication/core/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,59 @@ pub mod versioned {
155155
oidc: Option<oidc::v1alpha1::ClientAuthenticationOptions<OidcProductSpecificOptions>>,
156156
}
157157
}
158+
159+
#[cfg(test)]
160+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::AuthenticationClassSpec {
161+
fn roundtrip_test_data() -> Vec<Self> {
162+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
163+
- provider:
164+
static:
165+
userCredentialsSecret:
166+
name: simple-users-credentials
167+
- provider:
168+
ldap:
169+
hostname: my.ldap.server
170+
port: 389
171+
searchBase: ou=users,dc=example,dc=org
172+
searchFilter: foo
173+
bindCredentials:
174+
secretClass: openldap-bind-credentials
175+
ldapFieldNames:
176+
email: email
177+
givenName: givenName
178+
group: group
179+
surname: surname
180+
uid: uid
181+
tls:
182+
verification:
183+
server:
184+
caCert:
185+
secretClass: s3-cert
186+
- provider:
187+
oidc:
188+
hostname: my.keycloak.server
189+
port: 8080
190+
rootPath: /realms/master
191+
scopes:
192+
- email
193+
- openid
194+
- profile
195+
principalClaim: preferred_username
196+
providerHint: Keycloak
197+
tls:
198+
verification:
199+
server:
200+
caCert:
201+
secretClass: s3-cert
202+
- provider:
203+
tls: {}
204+
- provider:
205+
tls:
206+
clientCertSecretClass: client-auth-tls
207+
- provider:
208+
kerberos:
209+
kerberosSecretClass: kerberos-auth
210+
"})
211+
.expect("Failed to parse AuthenticationClassSpec YAML")
212+
}
213+
}

crates/stackable-operator/src/crd/listener/class/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,22 @@ pub mod versioned {
119119
pub service_overrides: Service,
120120
}
121121
}
122+
123+
#[cfg(test)]
124+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::ListenerClassSpec {
125+
fn roundtrip_test_data() -> Vec<Self> {
126+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
127+
- serviceType: ClusterIP
128+
- serviceType: NodePort
129+
- serviceType: LoadBalancer
130+
- serviceType: ClusterIP
131+
loadBalancerAllocateNodePorts: false
132+
loadBalancerClass: foo
133+
serviceAnnotations:
134+
foo: bar
135+
serviceExternalTrafficPolicy: Local
136+
preferredAddressType: HostnameConservative
137+
"})
138+
.expect("Failed to parse ListenerClassSpec YAML")
139+
}
140+
}

crates/stackable-operator/src/crd/listener/listeners/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,54 @@ pub mod versioned {
167167
Cluster,
168168
}
169169
}
170+
171+
#[cfg(test)]
172+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::ListenerSpec {
173+
fn roundtrip_test_data() -> Vec<Self> {
174+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
175+
- {}
176+
- className: cluster-internal
177+
extraPodLabelSelectorLabels: {}
178+
ports: []
179+
publishNotReadyAddresses: true
180+
- className: external-unstable
181+
extraPodLabelSelectorLabels:
182+
foo: bar
183+
ports:
184+
- name: http
185+
port: 8080
186+
protocol: TCP
187+
publishNotReadyAddresses: true
188+
"})
189+
.expect("Failed to parse ListenerSpec YAML")
190+
}
191+
}
192+
193+
#[cfg(test)]
194+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::PodListenersSpec {
195+
fn roundtrip_test_data() -> Vec<Self> {
196+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
197+
- listeners: {}
198+
- listeners:
199+
foo:
200+
scope: Node
201+
- listeners:
202+
foo:
203+
scope: Cluster
204+
ingressAddresses:
205+
- address: 1.2.3.4
206+
addressType: IP
207+
ports: {}
208+
- listeners:
209+
foo:
210+
scope: Cluster
211+
ingressAddresses:
212+
- address: foo.bar
213+
addressType: Hostname
214+
ports:
215+
http: 8080
216+
https: 8443
217+
"})
218+
.expect("Failed to parse PodListenersSpec YAML")
219+
}
220+
}

crates/stackable-operator/src/crd/s3/bucket/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,34 @@ pub mod versioned {
5151
pub connection: conn_v1alpha1::ConnectionSpec,
5252
}
5353
}
54+
55+
#[cfg(test)]
56+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::BucketSpec {
57+
fn roundtrip_test_data() -> Vec<Self> {
58+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
59+
- bucketName: my-example-bucket
60+
connection:
61+
reference: my-connection-resource
62+
- bucketName: foo
63+
connection:
64+
inline:
65+
host: s3.example.com
66+
- bucketName: foo
67+
connection:
68+
inline:
69+
host: s3.example.com
70+
port: 1234
71+
accessStyle: VirtualHosted
72+
credentials:
73+
secretClass: s3-credentials
74+
region:
75+
name: eu-west-1
76+
tls:
77+
verification:
78+
server:
79+
caCert:
80+
secretClass: s3-cert
81+
"})
82+
.expect("Failed to parse BucketSpec YAML")
83+
}
84+
}

crates/stackable-operator/src/crd/s3/connection/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,36 @@ pub mod versioned {
103103
}
104104
}
105105

106+
#[cfg(test)]
107+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::ConnectionSpec {
108+
fn roundtrip_test_data() -> Vec<Self> {
109+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
110+
- host: s3.example.com
111+
- host: s3.example.com
112+
port: 1234
113+
accessStyle: VirtualHosted
114+
credentials:
115+
secretClass: s3-credentials
116+
region:
117+
name: eu-west-1
118+
tls: null
119+
- host: s3.example.com
120+
region:
121+
name: us-east-1
122+
tls:
123+
verification:
124+
none: {}
125+
- host: s3.example.com
126+
tls:
127+
verification:
128+
server:
129+
caCert:
130+
secretClass: s3-cert
131+
"})
132+
.expect("Failed to parse ConnectionSpec YAML")
133+
}
134+
}
135+
106136
#[cfg(test)]
107137
mod tests {
108138
use std::collections::BTreeMap;

crates/stackable-operator/src/crd/scaler/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,16 @@ pub enum FailedInState {
105105
/// The `post_scale` hook returned an error.
106106
PostScaling,
107107
}
108+
109+
#[cfg(test)]
110+
impl stackable_versioned::test_utils::RoundtripTestData for v1alpha1::ScalerSpec {
111+
fn roundtrip_test_data() -> Vec<Self> {
112+
crate::utils::yaml_from_str_singleton_map(indoc::indoc! {"
113+
- replicas: 0
114+
- replicas: 1
115+
- replicas: 42
116+
- replicas: 65535
117+
"})
118+
.expect("Failed to parse ScalerSpec YAML")
119+
}
120+
}

crates/stackable-versioned-macros/src/codegen/container/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod r#enum;
2020
mod r#struct;
2121

2222
/// Contains common container data shared between structs and enums.
23+
#[derive(Debug)]
2324
pub struct CommonContainerData {
2425
/// Original attributes placed on the container, like `#[derive()]` or `#[cfg()]`.
2526
pub original_attributes: Vec<Attribute>,

crates/stackable-versioned-macros/src/codegen/container/struct/conversion.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{borrow::Cow, cmp::Ordering};
33
use indoc::formatdoc;
44
use itertools::Itertools as _;
55
use proc_macro2::TokenStream;
6-
use quote::quote;
6+
use quote::{format_ident, quote};
77
use syn::parse_quote;
88

99
use crate::{
@@ -527,6 +527,71 @@ impl Struct {
527527
})
528528
}
529529

530+
pub(super) fn generate_conversion_roundtrip_test(
531+
&self,
532+
versions: &[VersionDefinition],
533+
mod_gen_ctx: ModuleGenerationContext<'_>,
534+
spec_gen_ctx: &SpecGenerationContext<'_>,
535+
) -> TokenStream {
536+
let versioned_path = &*mod_gen_ctx.crates.versioned;
537+
let struct_ident = &self.common.idents.original;
538+
let kind_ident = &spec_gen_ctx.kubernetes_idents.kind;
539+
540+
let api_versions = spec_gen_ctx
541+
.version_strings
542+
.iter()
543+
.map(|version| {
544+
format!(
545+
"{group}/{version}",
546+
group = &spec_gen_ctx.kubernetes_arguments.group
547+
)
548+
})
549+
.collect::<Vec<_>>();
550+
551+
let earliest_version_module_ident = &versions
552+
.first()
553+
.expect("there must be at least one version present")
554+
.idents
555+
.module;
556+
let latest_version_module_ident = &versions
557+
.last()
558+
.expect("there must be at least one version present")
559+
.idents
560+
.module;
561+
let earliest_api_version = api_versions
562+
.first()
563+
.expect("there must be at least one version present");
564+
let latest_api_version = api_versions
565+
.last()
566+
.expect("there must be at least one version present");
567+
let test_function_down_up = format_ident!("{struct_ident}_roundtrip_down_up");
568+
let test_function_up_down = format_ident!("{struct_ident}_roundtrip_up_down");
569+
570+
quote! {
571+
#[cfg(test)]
572+
#[test]
573+
fn #test_function_down_up() {
574+
#versioned_path::test_utils::test_roundtrip::<#latest_version_module_ident::#struct_ident>(
575+
stringify!(#kind_ident),
576+
#latest_api_version,
577+
#earliest_api_version,
578+
#kind_ident::try_convert,
579+
);
580+
}
581+
582+
#[cfg(test)]
583+
#[test]
584+
fn #test_function_up_down() {
585+
#versioned_path::test_utils::test_roundtrip::<#earliest_version_module_ident::#struct_ident>(
586+
stringify!(#kind_ident),
587+
#earliest_api_version,
588+
#latest_api_version,
589+
#kind_ident::try_convert,
590+
);
591+
}
592+
}
593+
}
594+
530595
pub(super) fn generate_from_json_object_fn(
531596
&self,
532597
mod_gen_ctx: ModuleGenerationContext<'_>,

crates/stackable-versioned-macros/src/codegen/container/struct/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl Container {
8787
}
8888

8989
/// A versioned struct.
90+
#[derive(Debug)]
9091
pub struct Struct {
9192
/// List of fields defined in the original struct. How, and if, an item
9293
/// should generate code, is decided by the currently generated version.
@@ -101,6 +102,7 @@ pub struct Struct {
101102
pub generics: Generics,
102103
}
103104

105+
#[derive(Debug)]
104106
pub struct KubernetesData {
105107
pub kubernetes_arguments: StructCrdArguments,
106108
pub kubernetes_idents: KubernetesIdents,
@@ -164,11 +166,14 @@ impl Struct {
164166
self.generate_entry_impl_block(versions, mod_gen_ctx, &spec_gen_ctx);
165167
let version_enum = self.generate_version_enum(mod_gen_ctx, &spec_gen_ctx);
166168
let status_struct = self.generate_status_struct(mod_gen_ctx, &spec_gen_ctx);
169+
let conversion_roundtrip_test =
170+
self.generate_conversion_roundtrip_test(versions, mod_gen_ctx, &spec_gen_ctx);
167171

168172
container_tokens
169173
.extend_outer(entry_enum)
170174
.extend_outer(entry_enum_impl)
171175
.extend_outer(version_enum)
176+
.extend_outer(conversion_roundtrip_test)
172177
.extend_outer(status_struct);
173178
}
174179

crates/stackable-versioned-macros/src/codegen/item/field.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
utils::{ItemIdentExt, ItemIdents},
1818
};
1919

20+
#[derive(Debug)]
2021
pub struct VersionedField {
2122
pub original_attributes: Vec<Attribute>,
2223
pub changes: Option<BTreeMap<Version, ItemStatus>>,

0 commit comments

Comments
 (0)