1818
1919use crate :: attrs:: { IggyTestAttrs , TlsMode , Transport } ;
2020use crate :: params:: {
21- DetectedParam , analyze_signature, matrix_params, needs_client, needs_harness ,
22- needs_harness_mut, needs_mcp_client,
21+ DetectedParam , analyze_signature, fixture_params , matrix_params, needs_client, needs_fixtures ,
22+ needs_harness , needs_harness_mut, needs_mcp_client,
2323} ;
2424use proc_macro2:: { Span , TokenStream } ;
2525use quote:: { format_ident, quote} ;
@@ -174,20 +174,27 @@ fn generate_single_test(
174174 has_mcp_client : bool ,
175175 attrs : & IggyTestAttrs ,
176176) -> syn:: Result < TokenStream > {
177- let harness_setup = generate_harness_setup ( variant, has_client, has_mcp_client, attrs) ;
177+ let has_fixtures = needs_fixtures ( params) ;
178+ let fixture_setup = generate_fixture_setup ( params) ;
179+ let fixture_envs = generate_fixture_envs_collection ( params) ;
180+ let harness_setup =
181+ generate_harness_setup ( variant, has_client, has_mcp_client, has_fixtures, attrs) ;
178182 let client_setup = if has_client {
179183 generate_client_setup ( )
180184 } else {
181185 quote ! ( )
182186 } ;
183- let start_and_seed = generate_start_and_seed ( attrs) ;
187+ let fixture_seed = generate_fixture_seed ( params) ;
188+ let start_and_seed = generate_start_and_seed ( attrs, fixture_seed) ;
184189 let mcp_client_setup = generate_mcp_client_setup ( has_mcp_client) ;
185190 let param_bindings = generate_param_bindings ( params, has_harness, has_harness_mut) ;
186191
187192 Ok ( quote ! {
188193 #[ :: tokio:: test]
189194 #[ :: serial_test:: parallel]
190195 #fn_vis async fn #fn_name( ) {
196+ #fixture_setup
197+ #fixture_envs
191198 #harness_setup
192199 #start_and_seed
193200 #client_setup
@@ -212,24 +219,32 @@ fn generate_test_module(
212219 has_mcp_client : bool ,
213220 attrs : & IggyTestAttrs ,
214221) -> syn:: Result < TokenStream > {
222+ let has_fixtures = needs_fixtures ( params) ;
223+ let fixture_setup = generate_fixture_setup ( params) ;
224+ let fixture_envs = generate_fixture_envs_collection ( params) ;
225+ let fixture_seed = generate_fixture_seed ( params) ;
226+ let param_bindings = generate_param_bindings ( params, has_harness, has_harness_mut) ;
227+
215228 let mut test_fns = Vec :: new ( ) ;
216229
217230 for variant in variants {
218231 let test_name = format_ident ! ( "{}" , variant. suffix( ) ) ;
219- let harness_setup = generate_harness_setup ( variant, has_client, has_mcp_client, attrs) ;
232+ let harness_setup =
233+ generate_harness_setup ( variant, has_client, has_mcp_client, has_fixtures, attrs) ;
220234 let client_setup = if has_client {
221235 generate_client_setup ( )
222236 } else {
223237 quote ! ( )
224238 } ;
225- let start_and_seed = generate_start_and_seed ( attrs) ;
239+ let start_and_seed = generate_start_and_seed ( attrs, fixture_seed . clone ( ) ) ;
226240 let mcp_client_setup = generate_mcp_client_setup ( has_mcp_client) ;
227- let param_bindings = generate_param_bindings ( params, has_harness, has_harness_mut) ;
228241
229242 test_fns. push ( quote ! {
230243 #[ :: tokio:: test]
231244 #[ :: serial_test:: parallel]
232245 async fn #test_name( ) {
246+ #fixture_setup
247+ #fixture_envs
233248 #harness_setup
234249 #start_and_seed
235250 #client_setup
@@ -286,13 +301,18 @@ fn generate_impl_functions_for_test_matrix(
286301 } )
287302 . collect ( ) ;
288303
304+ let has_fixtures = needs_fixtures ( params) ;
305+ let fixture_setup = generate_fixture_setup ( params) ;
306+ let fixture_envs = generate_fixture_envs_collection ( params) ;
307+ let fixture_seed = generate_fixture_seed ( params) ;
289308 let param_bindings = generate_param_bindings ( params, has_harness, has_harness_mut) ;
290- let start_and_seed = generate_start_and_seed ( attrs) ;
309+ let start_and_seed = generate_start_and_seed ( attrs, fixture_seed . clone ( ) ) ;
291310 let mcp_client_setup = generate_mcp_client_setup ( has_mcp_client) ;
292311
293312 if variants. len ( ) == 1 {
294313 let variant = & variants[ 0 ] ;
295- let harness_setup = generate_harness_setup ( variant, has_client, has_mcp_client, attrs) ;
314+ let harness_setup =
315+ generate_harness_setup ( variant, has_client, has_mcp_client, has_fixtures, attrs) ;
296316 let client_setup = if has_client {
297317 generate_client_setup ( )
298318 } else {
@@ -304,6 +324,8 @@ fn generate_impl_functions_for_test_matrix(
304324 #[ :: tokio:: test]
305325 #[ :: serial_test:: parallel]
306326 #fn_vis async fn #fn_name( #( #param_names: #param_types) , * ) {
327+ #fixture_setup
328+ #fixture_envs
307329 #harness_setup
308330 #start_and_seed
309331 #client_setup
@@ -320,7 +342,8 @@ fn generate_impl_functions_for_test_matrix(
320342
321343 for variant in variants {
322344 let impl_name = format_ident ! ( "__impl_{}" , variant. suffix( ) ) ;
323- let harness_setup = generate_harness_setup ( variant, has_client, has_mcp_client, attrs) ;
345+ let harness_setup =
346+ generate_harness_setup ( variant, has_client, has_mcp_client, has_fixtures, attrs) ;
324347 let client_setup = if has_client {
325348 generate_client_setup ( )
326349 } else {
@@ -329,6 +352,8 @@ fn generate_impl_functions_for_test_matrix(
329352
330353 impl_fns. push ( quote ! {
331354 async fn #impl_name( #( #param_names: #param_types) , * ) {
355+ #fixture_setup
356+ #fixture_envs
332357 #harness_setup
333358 #start_and_seed
334359 #client_setup
@@ -363,6 +388,7 @@ fn generate_harness_setup(
363388 variant : & TestVariant ,
364389 has_client : bool ,
365390 has_mcp_client : bool ,
391+ has_fixtures : bool ,
366392 attrs : & IggyTestAttrs ,
367393) -> TokenStream {
368394 let transport = variant. transport . variant_ident ( ) ;
@@ -500,10 +526,18 @@ fn generate_harness_setup(
500526 . consumer_name
501527 . as_deref ( )
502528 . unwrap_or ( "connectors" ) ;
503- quote ! ( . connector( :: integration:: harness:: ConnectorConfig :: builder( )
504- . config_path( :: std:: path:: PathBuf :: from( #config_path) )
505- . consumer_name( #consumer_name)
506- . build( ) ) )
529+ if has_fixtures {
530+ quote ! ( . connector( :: integration:: harness:: ConnectorConfig :: builder( )
531+ . config_path( :: std:: path:: PathBuf :: from( #config_path) )
532+ . consumer_name( #consumer_name)
533+ . extra_envs( __fixture_envs. clone( ) )
534+ . build( ) ) )
535+ } else {
536+ quote ! ( . connector( :: integration:: harness:: ConnectorConfig :: builder( )
537+ . config_path( :: std:: path:: PathBuf :: from( #config_path) )
538+ . consumer_name( #consumer_name)
539+ . build( ) ) )
540+ }
507541 } else {
508542 quote ! ( )
509543 } ;
@@ -536,18 +570,35 @@ fn generate_client_setup() -> TokenStream {
536570///
537571/// When a seed function is present, uses `start_with_seed` to run seed
538572/// after server but before MCP and connector (which may depend on seed data).
539- fn generate_start_and_seed ( attrs : & IggyTestAttrs ) -> TokenStream {
540- match & attrs. seed_fn {
541- Some ( seed_fn) => {
542- // Wrap seed function in closure that takes ownership of client
543- // to avoid lifetime issues with async functions
573+ /// Fixture seeds are combined with the global seed.
574+ fn generate_start_and_seed ( attrs : & IggyTestAttrs , fixture_seed : TokenStream ) -> TokenStream {
575+ let has_fixture_seed = !fixture_seed. is_empty ( ) ;
576+ match ( & attrs. seed_fn , has_fixture_seed) {
577+ ( Some ( seed_fn) , true ) => {
578+ quote ! {
579+ __harness. start_with_seed( |__seed_client| async move {
580+ #seed_fn( & __seed_client) . await ?;
581+ #fixture_seed
582+ Ok ( ( ) )
583+ } ) . await . expect( "failed to start test harness" ) ;
584+ }
585+ }
586+ ( Some ( seed_fn) , false ) => {
544587 quote ! {
545588 __harness. start_with_seed( |__seed_client| async move {
546589 #seed_fn( & __seed_client) . await
547590 } ) . await . expect( "failed to start test harness" ) ;
548591 }
549592 }
550- None => {
593+ ( None , true ) => {
594+ quote ! {
595+ __harness. start_with_seed( |__seed_client| async move {
596+ #fixture_seed
597+ Ok ( ( ) )
598+ } ) . await . expect( "failed to start test harness" ) ;
599+ }
600+ }
601+ ( None , false ) => {
551602 quote ! {
552603 __harness. start( ) . await . expect( "failed to start test harness" ) ;
553604 }
@@ -566,6 +617,76 @@ fn generate_mcp_client_setup(has_mcp_client: bool) -> TokenStream {
566617 }
567618}
568619
620+ /// Generate fixture setup calls (before harness setup).
621+ fn generate_fixture_setup ( params : & [ DetectedParam ] ) -> TokenStream {
622+ let fixtures = fixture_params ( params) ;
623+ if fixtures. is_empty ( ) {
624+ return quote ! ( ) ;
625+ }
626+
627+ let setup_calls: Vec < _ > = fixtures
628+ . iter ( )
629+ . filter_map ( |p| {
630+ if let DetectedParam :: Fixture { name, ty } = p {
631+ let var_name = format_ident ! ( "__fixture_{}" , name) ;
632+ Some ( quote ! {
633+ let #var_name = <#ty as :: integration:: harness:: TestFixture >:: setup( )
634+ . await
635+ . expect( "failed to setup fixture" ) ;
636+ } )
637+ } else {
638+ None
639+ }
640+ } )
641+ . collect ( ) ;
642+
643+ quote ! ( #( #setup_calls) * )
644+ }
645+
646+ /// Generate fixture envs collection (after fixture setup, before harness).
647+ fn generate_fixture_envs_collection ( params : & [ DetectedParam ] ) -> TokenStream {
648+ let fixtures = fixture_params ( params) ;
649+ if fixtures. is_empty ( ) {
650+ return quote ! ( ) ;
651+ }
652+
653+ let env_calls: Vec < _ > = fixtures
654+ . iter ( )
655+ . filter_map ( |p| {
656+ if let DetectedParam :: Fixture { name, .. } = p {
657+ let var_name = format_ident ! ( "__fixture_{}" , name) ;
658+ Some ( quote ! {
659+ __fixture_envs. extend(
660+ :: integration:: harness:: TestFixture :: connector_envs( & #var_name)
661+ ) ;
662+ } )
663+ } else {
664+ None
665+ }
666+ } )
667+ . collect ( ) ;
668+
669+ quote ! {
670+ let mut __fixture_envs = :: std:: collections:: HashMap :: <String , String >:: new( ) ;
671+ #( #env_calls) *
672+ }
673+ }
674+
675+ /// Generate fixture seed calls (inside start_with_seed closure).
676+ ///
677+ /// Note: Currently disabled to avoid move semantics issues with async closures.
678+ /// Fixtures that need to seed data should do so in the test body after harness start.
679+ fn generate_fixture_seed ( _params : & [ DetectedParam ] ) -> TokenStream {
680+ // Fixture seeding is disabled for now because:
681+ // 1. The async move closure in start_with_seed captures the fixture by value
682+ // 2. This prevents using the fixture in the test body after seeding
683+ // 3. Most fixtures don't need to seed data - they just provide env vars
684+ //
685+ // If a fixture needs to seed data, it can be done manually in the test body:
686+ // fixture.seed(&client).await.unwrap();
687+ quote ! ( )
688+ }
689+
569690fn generate_param_bindings (
570691 params : & [ DetectedParam ] ,
571692 _has_harness : bool ,
@@ -595,6 +716,12 @@ fn generate_param_bindings(
595716 let #name = __mcp_client;
596717 } ) ;
597718 }
719+ DetectedParam :: Fixture { name, .. } => {
720+ let fixture_var = format_ident ! ( "__fixture_{}" , name) ;
721+ bindings. push ( quote ! {
722+ let #name = #fixture_var;
723+ } ) ;
724+ }
598725 DetectedParam :: MatrixParam { .. } => { }
599726 }
600727 }
0 commit comments