Skip to content

Commit 30d9681

Browse files
committed
feat(stackable-versioned)!: Automatically generate conversion roundtrip tests
1 parent 7486017 commit 30d9681

25 files changed

Lines changed: 700 additions & 3 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stackable-operator/src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ impl Client {
590590
pub trait GetApi: Resource + Sized {
591591
/// The namespace type for `Self`'s scope.
592592
///
593-
/// This will be [`str`] for namespaced resource, and [`()`] for cluster-scoped resources.
593+
/// This will be [`str`] for namespaced resource, and `()` for cluster-scoped resources.
594594
type Namespace: ?Sized;
595595
/// Get a [`kube::Api`] for `Self`'s native scope..
596596
fn get_api(client: kube::Client, ns: &Self::Namespace) -> kube::Api<Self>

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<O>>,
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-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::{
@@ -530,6 +530,71 @@ impl Struct {
530530
})
531531
}
532532

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

0 commit comments

Comments
 (0)