@@ -14,6 +14,7 @@ import (
1414	"slices" 
1515	"sort" 
1616	"strings" 
17+ 	"sync" 
1718	"testing" 
1819
1920	"github.com/gruntwork-io/terratest/modules/terraform" 
@@ -425,6 +426,10 @@ func runParityTest(t *testing.T, modules []*Module, config *Config) {
425426		}
426427		// Destroy all modules 
427428		for  _ , module  :=  range  modules  {
429+ 			terraform .WithDefaultRetryableErrors (t , module .Options )
430+ 			if  _ , err  :=  terraform .InitE (t , module .Options ); err  !=  nil  {
431+ 				t .Logf ("cleanup init error for %s: %v" , module .Name , err )
432+ 			}
428433			if  err  :=  module .Destroy (ctx , t ); err  !=  nil  {
429434				t .Logf ("cleanup error for %s: %v" , module .Name , err )
430435			}
@@ -435,59 +440,87 @@ func runParityTest(t *testing.T, modules []*Module, config *Config) {
435440	var  (
436441		modulesWithMajor  []string 
437442		modulesWithMinor  []string 
443+ 		planErrors        []string 
438444	)
445+ 	var  mu  sync.Mutex 
439446
440447	for  _ , module  :=  range  modules  {
441- 		if  slices .Contains (config .ExceptionList , module .Name ) {
442- 			t .Logf ("Skipping parity check for %s as it is in the exception list" , module .Name )
443- 			continue 
444- 		}
448+ 		mod  :=  module 
449+ 		t .Run (mod .Name + "_parity" , func (t  * testing.T ) {
450+ 			t .Parallel ()
445451
446- 		if  module .ApplyFailed  {
447- 			t .Logf ("Skipping parity check for %s as registry apply failed" , module .Name )
448- 			continue 
449- 		}
450- 
451- 		planStruct , err  :=  module .PlanWithStruct (t )
452- 		if  err  !=  nil  {
453- 			t .Fatalf ("failed to plan %s with local paths: %v" , module .Name , err )
454- 		}
452+ 			if  slices .Contains (config .ExceptionList , mod .Name ) {
453+ 				t .Logf ("Skipping parity check for %s as it is in the exception list" , mod .Name )
454+ 				return 
455+ 			}
455456
456- 		summary  :=  summarizePlanChanges (planStruct )
457- 		moduleSummaries [module .Name ] =  summary 
457+ 			if  mod .ApplyFailed  {
458+ 				t .Logf ("Skipping parity check for %s as registry apply failed" , mod .Name )
459+ 				return 
460+ 			}
458461
459- 		switch  {
460- 		case  summary .hasMajorDrift ():
461- 			t .Logf ("parity major drift: %s triggers replacements/destroys" , module .Name )
462- 			if  len (summary .ReplaceAddresses ) >  0  {
463- 				t .Logf ("  replacements: %v" , summary .ReplaceAddresses )
462+ 			planStruct , err  :=  mod .PlanWithStruct (t )
463+ 			if  err  !=  nil  {
464+ 				t .Logf ("parity error: failed to plan %s with local paths: %v" , mod .Name , err )
465+ 				mu .Lock ()
466+ 				planErrors  =  append (planErrors , fmt .Sprintf ("%s: %v" , mod .Name , err ))
467+ 				mu .Unlock ()
468+ 				return 
464469			}
465- 			if  len (summary .DestroyAddresses ) >  0  {
466- 				t .Logf ("  destroys: %v" , summary .DestroyAddresses )
470+ 
471+ 			summary  :=  summarizePlanChanges (planStruct )
472+ 
473+ 			mu .Lock ()
474+ 			moduleSummaries [mod .Name ] =  summary 
475+ 			switch  {
476+ 			case  summary .hasMajorDrift ():
477+ 				modulesWithMajor  =  append (modulesWithMajor , mod .Name )
478+ 			case  summary .hasChanges ():
479+ 				modulesWithMinor  =  append (modulesWithMinor , mod .Name )
467480			}
468- 			if  len (summary .OtherAddresses ) >  0  {
469- 				t .Logf ("  other actions: %v" , summary .OtherAddresses )
481+ 			mu .Unlock ()
482+ 
483+ 			switch  {
484+ 			case  summary .hasMajorDrift ():
485+ 				t .Logf ("parity major drift: %s triggers replacements/destroys" , mod .Name )
486+ 				if  len (summary .ReplaceAddresses ) >  0  {
487+ 					t .Logf ("  replacements: %v" , summary .ReplaceAddresses )
488+ 				}
489+ 				if  len (summary .DestroyAddresses ) >  0  {
490+ 					t .Logf ("  destroys: %v" , summary .DestroyAddresses )
491+ 				}
492+ 				if  len (summary .OtherAddresses ) >  0  {
493+ 					t .Logf ("  other actions: %v" , summary .OtherAddresses )
494+ 				}
495+ 			case  summary .hasChanges ():
496+ 				t .Logf ("parity drift detected: %s has additions/updates only (adds=%d, updates=%d)" , mod .Name , summary .Adds , summary .Updates )
497+ 			default :
498+ 				t .Logf ("parity confirmed: %s produces identical infrastructure with local paths" , mod .Name )
470499			}
471- 			modulesWithMajor  =  append (modulesWithMajor , module .Name )
472- 		case  summary .hasChanges ():
473- 			t .Logf ("parity drift detected: %s has additions/updates only (adds=%d, updates=%d)" , module .Name , summary .Adds , summary .Updates )
474- 			modulesWithMinor  =  append (modulesWithMinor , module .Name )
475- 		default :
476- 			t .Logf ("parity confirmed: %s produces identical infrastructure with local paths" , module .Name )
500+ 		})
501+ 	}
502+ 
503+ 	if  len (planErrors ) >  0  {
504+ 		sort .Strings (planErrors )
505+ 		t .Logf ("parity check initialization errors:" )
506+ 		for  _ , errMsg  :=  range  planErrors  {
507+ 			t .Logf ("- %s" , errMsg )
477508		}
509+ 		t .Fatalf ("registry and local modules failed to re-initialize for %d module(s)" , len (planErrors ))
478510	}
479511
480512	if  len (modulesWithMajor ) >  0  {
481- 		t . Logf ( "parity check summary (major drift):" )
513+ 		sort . Strings ( modulesWithMajor )
482514		for  _ , moduleName  :=  range  modulesWithMajor  {
483515			summary  :=  moduleSummaries [moduleName ]
484- 			t .Logf ("- %s:  %d replacements,  %d destroys" , moduleName , summary .Replaces , summary .Deletes )
516+ 			t .Logf ("parity major drift: %s requires  %d replacements and  %d destroys" , moduleName , summary .Replaces , summary .Deletes )
485517		}
486518		t .Logf ("these changes will force resource recreation and require a major version bump" )
487519		t .Fatalf ("registry and local modules are out of parity for %d module(s)" , len (modulesWithMajor ))
488520	}
489521
490522	if  len (modulesWithMinor ) >  0  {
523+ 		sort .Strings (modulesWithMinor )
491524		t .Logf ("parity drift summary (non-breaking): %v" , modulesWithMinor )
492525	}
493526
0 commit comments