11package test
22
33import (
4+ "context"
5+ "crypto/md5"
46 "fmt"
57 "net"
68 "os"
@@ -182,7 +184,7 @@ func ValidateRAMResourceShare(t *testing.T, region, shareArn string, expectedPri
182184 }
183185}
184186
185- // ValidateDNSResolution performs a basic DNS resolution test for a domain with timeout
187+ // ValidateDNSResolution performs DNS resolution test with context cancellation support
186188func ValidateDNSResolution (t * testing.T , domain string , expectedIPs []string ) {
187189 if ! strings .HasSuffix (domain , "." ) {
188190 domain += "."
@@ -191,51 +193,57 @@ func ValidateDNSResolution(t *testing.T, domain string, expectedIPs []string) {
191193 // Remove trailing dot for DNS lookup
192194 lookupDomain := strings .TrimSuffix (domain , "." )
193195
194- // Create a channel to handle timeout
195- type dnsResult struct {
196- ips []net.IP
197- err error
196+ // Create context with timeout for proper cancellation
197+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
198+ defer cancel ()
199+
200+ // Use context-aware DNS resolver
201+ resolver := & net.Resolver {
202+ PreferGo : true ,
203+ Dial : func (ctx context.Context , network , address string ) (net.Conn , error ) {
204+ d := net.Dialer {
205+ Timeout : 3 * time .Second ,
206+ }
207+ return d .DialContext (ctx , network , address )
208+ },
198209 }
199210
200- result := make (chan dnsResult , 1 )
211+ // Perform context-aware DNS lookup
212+ ips , err := resolver .LookupIPAddr (ctx , lookupDomain )
201213
202- // Perform DNS lookup in a goroutine with timeout
203- go func () {
204- ips , err := net .LookupIP (lookupDomain )
205- result <- dnsResult {ips : ips , err : err }
206- }()
214+ if err != nil {
215+ // Check if it was a context cancellation (timeout)
216+ if ctx .Err () == context .DeadlineExceeded {
217+ t .Logf ("Warning: DNS lookup for %s timed out after 5 seconds (expected in test environment)" , lookupDomain )
218+ } else {
219+ t .Logf ("Warning: DNS lookup for %s failed: %v (expected in test environment)" , lookupDomain , err )
220+ }
221+ return
222+ }
207223
208- // Wait for result or timeout (5 seconds)
209- select {
210- case res := <- result :
211- if res .err != nil {
212- t .Logf ("Warning: DNS lookup for %s failed: %v (expected in test environment)" , lookupDomain , res .err )
213- return
224+ // Validate expected IPs if provided
225+ if len (expectedIPs ) > 0 {
226+ actualIPs := make ([]string , 0 )
227+ for _ , ipAddr := range ips {
228+ actualIPs = append (actualIPs , ipAddr .IP .String ())
214229 }
215-
216- if len (expectedIPs ) > 0 {
217- actualIPs := make ([]string , 0 )
218- for _ , ip := range res .ips {
219- actualIPs = append (actualIPs , ip .String ())
220- }
221230
222- for _ , expectedIP := range expectedIPs {
223- found := false
224- for _ , actualIP := range actualIPs {
225- if actualIP == expectedIP {
226- found = true
227- break
228- }
229- }
230- if ! found {
231- t .Logf ("Warning: Expected IP %s not found in DNS resolution for %s (got: %v)" ,
232- expectedIP , lookupDomain , actualIPs )
231+ for _ , expectedIP := range expectedIPs {
232+ found := false
233+ for _ , actualIP := range actualIPs {
234+ if actualIP == expectedIP {
235+ found = true
236+ break
233237 }
234238 }
239+ if ! found {
240+ t .Logf ("Warning: Expected IP %s not found in DNS resolution for %s (got: %v)" ,
241+ expectedIP , lookupDomain , actualIPs )
242+ }
235243 }
236- case <- time .After (5 * time .Second ):
237- t .Logf ("Warning: DNS lookup for %s timed out after 5 seconds (expected in test environment)" , lookupDomain )
238244 }
245+
246+ t .Logf ("✓ DNS resolution completed for %s with context cancellation support" , lookupDomain )
239247}
240248
241249// WaitForResolverRuleDeletion waits for a resolver rule to be deleted with exponential backoff
@@ -413,13 +421,32 @@ func CleanupTestResolverRules(t *testing.T, region string, namePrefix string) {
413421 require .NoError (t , err )
414422 svc := route53resolver .New (sess )
415423
416- // List all resolver rules with retry logic
424+ // List resolver rules with proper filtering to reduce API calls
417425 maxRetries := 3
418426 baseDelay := time .Second
419427 var result * route53resolver.ListResolverRulesOutput
420428
429+ // Add filters to reduce unnecessary API calls
430+ filters := []* route53resolver.Filter {
431+ {
432+ Name : aws .String ("TYPE" ),
433+ Values : []* string {aws .String ("FORWARD" )}, // Only get FORWARD rules
434+ },
435+ }
436+
437+ // If we have a specific name prefix, add name filter
438+ if namePrefix != "" && len (namePrefix ) > 5 {
439+ filters = append (filters , & route53resolver.Filter {
440+ Name : aws .String ("NAME-REGEX" ),
441+ Values : []* string {aws .String (fmt .Sprintf ("%s.*" , namePrefix ))},
442+ })
443+ }
444+
421445 for attempt := 0 ; attempt < maxRetries ; attempt ++ {
422- input := & route53resolver.ListResolverRulesInput {}
446+ input := & route53resolver.ListResolverRulesInput {
447+ Filters : filters ,
448+ MaxResults : aws .Int64 (50 ), // Limit results to reduce response size
449+ }
423450 result , err = svc .ListResolverRules (input )
424451 if err == nil {
425452 break
@@ -518,7 +545,109 @@ func getAWSAccountID(t *testing.T, sess *session.Session) string {
518545// GenerateTestResourceName creates a safe test resource name with proper prefixes
519546func GenerateTestResourceName (resourceType , testName string ) string {
520547 uniqueID := strings .ToLower (random .UniqueId ())
521- return fmt .Sprintf ("terratest-%s-%s-%s" , resourceType , testName , uniqueID )
548+
549+ // Handle special cases for AWS resource format compliance
550+ switch resourceType {
551+ case "account" :
552+ // Generate mock AWS account ID (12 digits, starts with 000 for safety)
553+ hash := fmt .Sprintf ("%x" , random .UniqueId ())
554+ if len (hash ) > 9 {
555+ hash = hash [:9 ]
556+ }
557+ return fmt .Sprintf ("000%09s" , hash )
558+ case "resolver-endpoint" :
559+ // Generate AWS resolver endpoint format: rslvr-out-[17 hex chars]
560+ hash := fmt .Sprintf ("%x" , random .UniqueId ())
561+ if len (hash ) > 17 {
562+ hash = hash [:17 ]
563+ }
564+ return fmt .Sprintf ("rslvr-out-%017s" , hash )
565+ case "resolver-rule" :
566+ // Generate AWS resolver rule format: rslvr-rr-[17 hex chars]
567+ hash := fmt .Sprintf ("%x" , random .UniqueId ())
568+ if len (hash ) > 17 {
569+ hash = hash [:17 ]
570+ }
571+ return fmt .Sprintf ("rslvr-rr-%017s" , hash )
572+ case "vpc" :
573+ // Generate AWS VPC format: vpc-[8 or 17 hex chars] - using 8 for simplicity
574+ hash := fmt .Sprintf ("%x" , random .UniqueId ())
575+ if len (hash ) > 8 {
576+ hash = hash [:8 ]
577+ }
578+ return fmt .Sprintf ("vpc-%08s" , hash )
579+ case "sg" :
580+ // Generate AWS Security Group format: sg-[8 or 17 hex chars]
581+ hash := fmt .Sprintf ("%x" , random .UniqueId ())
582+ if len (hash ) > 8 {
583+ hash = hash [:8 ]
584+ }
585+ return fmt .Sprintf ("sg-%08s" , hash )
586+ default :
587+ // Default terratest naming for other resources
588+ return fmt .Sprintf ("terratest-%s-%s-%s" , resourceType , testName , uniqueID )
589+ }
590+ }
591+
592+ // GenerateTestSessionID creates a unique session ID for parallel test isolation
593+ func GenerateTestSessionID (t * testing.T ) string {
594+ // Create unique session ID based on test name, timestamp, and random data
595+ testName := t .Name ()
596+ timestamp := time .Now ().UnixNano ()
597+ randomData := random .UniqueId ()
598+
599+ // Create MD5 hash for consistent length
600+ hash := md5 .Sum ([]byte (fmt .Sprintf ("%s-%d-%s" , testName , timestamp , randomData )))
601+ sessionID := fmt .Sprintf ("%x" , hash )[:12 ] // Use first 12 chars for session ID
602+
603+ t .Logf ("Generated test session ID: %s for test: %s" , sessionID , testName )
604+ return sessionID
605+ }
606+
607+ // GenerateTestResourceNameWithSession creates resource names with session isolation
608+ func GenerateTestResourceNameWithSession (resourceType , testName , sessionID string ) string {
609+ // Handle special cases for AWS resource format compliance with session isolation
610+ switch resourceType {
611+ case "account" :
612+ // Generate unique mock AWS account ID with session isolation
613+ hash := fmt .Sprintf ("%x" , fmt .Sprintf ("%s-%s" , sessionID , testName ))
614+ if len (hash ) > 9 {
615+ hash = hash [:9 ]
616+ }
617+ return fmt .Sprintf ("000%09s" , hash )
618+ case "resolver-endpoint" :
619+ // Generate unique AWS resolver endpoint format with session
620+ hash := fmt .Sprintf ("%x" , fmt .Sprintf ("%s-%s" , sessionID , testName ))
621+ if len (hash ) > 17 {
622+ hash = hash [:17 ]
623+ }
624+ return fmt .Sprintf ("rslvr-out-%017s" , hash )
625+ case "resolver-rule" :
626+ // Generate unique AWS resolver rule format with session
627+ hash := fmt .Sprintf ("%x" , fmt .Sprintf ("%s-%s" , sessionID , testName ))
628+ if len (hash ) > 17 {
629+ hash = hash [:17 ]
630+ }
631+ return fmt .Sprintf ("rslvr-rr-%017s" , hash )
632+ case "vpc" :
633+ // Generate unique AWS VPC format with session
634+ hash := fmt .Sprintf ("%x" , fmt .Sprintf ("%s-%s" , sessionID , testName ))
635+ if len (hash ) > 8 {
636+ hash = hash [:8 ]
637+ }
638+ return fmt .Sprintf ("vpc-%08s" , hash )
639+ case "sg" :
640+ // Generate unique AWS Security Group format with session
641+ hash := fmt .Sprintf ("%x" , fmt .Sprintf ("%s-%s" , sessionID , testName ))
642+ if len (hash ) > 8 {
643+ hash = hash [:8 ]
644+ }
645+ return fmt .Sprintf ("sg-%08s" , hash )
646+ default :
647+ // Default terratest naming with session isolation
648+ uniqueID := strings .ToLower (random .UniqueId ())
649+ return fmt .Sprintf ("terratest-%s-%s-%s-%s" , resourceType , testName , sessionID , uniqueID )
650+ }
522651}
523652
524653// ValidateResourceNameFormat validates that resource names follow safe testing patterns
@@ -775,6 +904,51 @@ func VerifyTestEnvironment(t *testing.T, region string) {
775904 t .Logf ("✓ Test environment verified: region=%s" , region )
776905}
777906
907+ // ValidateAWSResourceFormats validates that mock AWS resource IDs follow proper formats
908+ func ValidateAWSResourceFormats (t * testing.T , resourceMap map [string ]string ) {
909+ for resourceType , resourceID := range resourceMap {
910+ switch resourceType {
911+ case "account" :
912+ require .Regexp (t , `^[0-9]{12}$` , resourceID ,
913+ "AWS Account ID must be exactly 12 digits: %s" , resourceID )
914+ require .True (t , strings .HasPrefix (resourceID , "000" ),
915+ "Test Account ID should start with '000' for safety: %s" , resourceID )
916+ case "resolver-endpoint" :
917+ require .Regexp (t , `^rslvr-out-[a-f0-9]{17}$` , resourceID ,
918+ "Resolver endpoint ID must match format 'rslvr-out-[17 hex chars]': %s" , resourceID )
919+ case "resolver-rule" :
920+ require .Regexp (t , `^rslvr-rr-[a-f0-9]{17}$` , resourceID ,
921+ "Resolver rule ID must match format 'rslvr-rr-[17 hex chars]': %s" , resourceID )
922+ case "vpc" :
923+ require .Regexp (t , `^vpc-[a-f0-9]{8}([a-f0-9]{9})?$` , resourceID ,
924+ "VPC ID must match format 'vpc-[8 or 17 hex chars]': %s" , resourceID )
925+ case "security-group" :
926+ require .Regexp (t , `^sg-[a-f0-9]{8}([a-f0-9]{9})?$` , resourceID ,
927+ "Security Group ID must match format 'sg-[8 or 17 hex chars]': %s" , resourceID )
928+ case "subnet" :
929+ require .Regexp (t , `^subnet-[a-f0-9]{8}([a-f0-9]{9})?$` , resourceID ,
930+ "Subnet ID must match format 'subnet-[8 or 17 hex chars]': %s" , resourceID )
931+ default :
932+ t .Logf ("Warning: Unknown resource type '%s' for validation: %s" , resourceType , resourceID )
933+ }
934+ t .Logf ("✓ AWS resource format validated: %s = %s" , resourceType , resourceID )
935+ }
936+ }
937+
938+ // ValidateResourceIDUniqueness ensures no duplicate resource IDs across parallel tests
939+ func ValidateResourceIDUniqueness (t * testing.T , resourceIDs []string , sessionID string ) {
940+ seenIDs := make (map [string ]bool )
941+
942+ for _ , resourceID := range resourceIDs {
943+ if seenIDs [resourceID ] {
944+ t .Errorf ("Duplicate resource ID detected: %s (session: %s)" , resourceID , sessionID )
945+ }
946+ seenIDs [resourceID ] = true
947+ }
948+
949+ t .Logf ("✓ Resource ID uniqueness validated for session %s: %d resources" , sessionID , len (resourceIDs ))
950+ }
951+
778952// isRetryableAWSError determines if an AWS error is retryable
779953func isRetryableAWSError (err error ) bool {
780954 // Check for specific AWS error types that are retryable
0 commit comments