@@ -111,7 +111,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth
111111 {
112112 get
113113 {
114- SqlConnectionStringBuilder builder = new ( TCPConnectionString ) ;
114+ SqlConnectionStringBuilder builder = new ( TCPConnectionString ) ;
115115 return builder . Authentication == SqlAuthenticationMethod . SqlPassword || builder . Authentication == SqlAuthenticationMethod . NotSpecified ;
116116 }
117117 }
@@ -556,59 +556,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6()
556556 }
557557 }
558558
559+ // Generate a new GUID and return the characters from its 1st and 4th
560+ // parts, as shown here:
561+ //
562+ // 7ff01cb8-88c7-11f0-b433-00155d7e531e
563+ // ^^^^^^^^ ^^^^
564+ //
565+ // These 12 characters are concatenated together without any
566+ // separators. These 2 parts typically comprise a timestamp and clock
567+ // sequence, most likely to be unique for tests that generate names in
568+ // quick succession.
569+ private static string GetGuidParts ( )
570+ {
571+ var guid = Guid . NewGuid ( ) . ToString ( ) ;
572+ // GOTCHA: The slice operator is inclusive of the start index and
573+ // exclusive of the end index!
574+ return guid . Substring ( 0 , 8 ) + guid . Substring ( 19 , 4 ) ;
575+ }
576+
559577 /// <summary>
560- /// Generate a unique name to use in Sql Server;
561- /// some providers does not support names (Oracle supports up to 30).
578+ /// Generate a short unique database object name, whose maximum length
579+ /// is 30 characters, with the format:
580+ ///
581+ /// <Prefix>_<GuidParts>
582+ ///
583+ /// The Prefix will be truncated to satisfy the overall maximum length.
584+ ///
585+ /// The GUID parts will be the characters from the 1st and 4th blocks
586+ /// from a traditional string representation, as shown here:
587+ ///
588+ /// 7ff01cb8-88c7-11f0-b433-00155d7e531e
589+ /// ^^^^^^^^ ^^^^
590+ ///
591+ /// These 2 parts typically comprise a timestamp and clock sequence,
592+ /// most likely to be unique for tests that generate names in quick
593+ /// succession. The 12 characters are concatenated together without any
594+ /// separators.
562595 /// </summary>
563- /// <param name="prefix">The name length will be no more then (16 + prefix.Length + escapeLeft.Length + escapeRight.Length).</param>
564- /// <param name="withBracket">Name without brackets.</param>
565- /// <returns>Unique name by considering the Sql Server naming rules.</returns>
566- public static string GetUniqueName ( string prefix , bool withBracket = true )
567- {
568- string escapeLeft = withBracket ? "[" : string . Empty ;
569- string escapeRight = withBracket ? "]" : string . Empty ;
570- string uniqueName = string . Format ( "{0}{1}_{2}_{3}{4}" ,
571- escapeLeft ,
572- prefix ,
573- DateTime . Now . Ticks . ToString ( "X" , CultureInfo . InvariantCulture ) , // up to 8 characters
574- Guid . NewGuid ( ) . ToString ( ) . Substring ( 0 , 6 ) , // take the first 6 characters only
575- escapeRight ) ;
576- return uniqueName ;
596+ ///
597+ /// <param name="prefix">
598+ /// The prefix to use when generating the unique name, truncated to at
599+ /// most 18 characters when withBracket is false, and 16 characters when
600+ /// withBracket is true.
601+ ///
602+ /// This should not contain any characters that cannot be used in
603+ /// database object names. See:
604+ ///
605+ /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
606+ /// </param>
607+ ///
608+ /// <param name="withBracket">
609+ /// When true, the entire generated name will be enclosed in square
610+ /// brackets, for example:
611+ ///
612+ /// [MyPrefix_7ff01cb811f0]
613+ /// </param>
614+ ///
615+ /// <returns>
616+ /// A unique database object name, no more than 30 characters long.
617+ /// </returns>
618+ public static string GetShortName ( string prefix , bool withBracket = true )
619+ {
620+ StringBuilder name = new ( 30 ) ;
621+
622+ if ( withBracket )
623+ {
624+ name . Append ( '[' ) ;
625+ }
626+
627+ int maxPrefixLength = withBracket ? 16 : 18 ;
628+ if ( prefix . Length > maxPrefixLength )
629+ {
630+ prefix = prefix . Substring ( 0 , maxPrefixLength ) ;
631+ }
632+
633+ name . Append ( prefix ) ;
634+ name . Append ( '_' ) ;
635+ name . Append ( GetGuidParts ( ) ) ;
636+
637+ if ( withBracket )
638+ {
639+ name . Append ( ']' ) ;
640+ }
641+
642+ return name . ToString ( ) ;
577643 }
578644
579645 /// <summary>
580- /// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date
581- /// to generate a unique name to use in Sql Server;
582- /// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting.
646+ /// Generate a long unique database object name, whose maximum length is
647+ /// 96 characters, with the format:
648+ ///
649+ /// <Prefix>_<GuidParts>_<UserName>_<MachineName>
650+ ///
651+ /// The Prefix will be truncated to satisfy the overall maximum length.
652+ ///
653+ /// The GUID Parts will be the characters from the 1st and 4th blocks
654+ /// from a traditional string representation, as shown here:
655+ ///
656+ /// 7ff01cb8-88c7-11f0-b433-00155d7e531e
657+ /// ^^^^^^^^ ^^^^
658+ ///
659+ /// These 2 parts typically comprise a timestamp and clock sequence,
660+ /// most likely to be unique for tests that generate names in quick
661+ /// succession. The 12 characters are concatenated together without any
662+ /// separators.
663+ ///
664+ /// The UserName and MachineName are obtained from the Environment,
665+ /// and will be truncated to satisfy the maximum overall length.
583666 /// </summary>
584- /// <param name="prefix">Add the prefix to the generate string.</param>
585- /// <param name="withBracket">Database name must be pass with brackets by default.</param>
586- /// <returns>Unique name by considering the Sql Server naming rules, never longer than 96 characters.</returns>
587- public static string GetUniqueNameForSqlServer ( string prefix , bool withBracket = true )
588- {
589- string extendedPrefix = string . Format (
590- "{0}_{1}_{2}@{3}" ,
591- prefix ,
592- Environment . UserName ,
593- Environment . MachineName ,
594- DateTime . Now . ToString ( "yyyy_MM_dd" , CultureInfo . InvariantCulture ) ) ;
595- string name = GetUniqueName ( extendedPrefix , withBracket ) ;
596-
597- // Truncate to no more than 96 characters.
598- const int maxLen = 96 ;
599- if ( name . Length > maxLen )
600- {
601- if ( withBracket )
602- {
603- name = name . Substring ( 0 , maxLen - 1 ) + ']' ;
604- }
605- else
606- {
607- name = name . Substring ( 0 , maxLen ) ;
608- }
667+ ///
668+ /// <param name="prefix">
669+ /// The prefix to use when generating the unique name, truncated to at
670+ /// most 32 characters.
671+ ///
672+ /// This should not contain any characters that cannot be used in
673+ /// database object names. See:
674+ ///
675+ /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
676+ /// </param>
677+ ///
678+ /// <param name="withBracket">
679+ /// When true, the entire generated name will be enclosed in square
680+ /// brackets, for example:
681+ ///
682+ /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name]
683+ /// </param>
684+ ///
685+ /// <returns>
686+ /// A unique database object name, no more than 96 characters long.
687+ /// </returns>
688+ public static string GetLongName ( string prefix , bool withBracket = true )
689+ {
690+ StringBuilder name = new ( 96 ) ;
691+
692+ if ( withBracket )
693+ {
694+ name . Append ( '[' ) ;
695+ }
696+
697+ if ( prefix . Length > 32 )
698+ {
699+ prefix = prefix . Substring ( 0 , 32 ) ;
609700 }
610701
611- return name ;
702+ name . Append ( prefix ) ;
703+ name . Append ( '_' ) ;
704+ name . Append ( GetGuidParts ( ) ) ;
705+ name . Append ( '_' ) ;
706+
707+ var suffix =
708+ Environment . UserName + '_' +
709+ Environment . MachineName ;
710+
711+ int maxSuffixLength = 96 - name . Length ;
712+ if ( withBracket )
713+ {
714+ -- maxSuffixLength ;
715+ }
716+ if ( suffix . Length > maxSuffixLength )
717+ {
718+ suffix = suffix . Substring ( 0 , maxSuffixLength ) ;
719+ }
720+
721+ name . Append ( suffix ) ;
722+
723+ if ( withBracket )
724+ {
725+ name . Append ( ']' ) ;
726+ }
727+
728+ return name . ToString ( ) ;
612729 }
613730
614731 public static void CreateTable ( SqlConnection sqlConnection , string tableName , string createBody )
@@ -1101,6 +1218,8 @@ protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData)
11011218
11021219 public readonly ref struct XEventScope // : IDisposable
11031220 {
1221+ private const int MaxXEventsLatencyS = 5 ;
1222+
11041223 private readonly SqlConnection _connection ;
11051224 private readonly bool _useDatabaseSession ;
11061225
@@ -1135,6 +1254,8 @@ INNER JOIN sys.dm_xe_sessions AS xe
11351254
11361255 using ( SqlCommand command = new SqlCommand ( xEventQuery , _connection ) )
11371256 {
1257+ Thread . Sleep ( MaxXEventsLatencyS * 1000 ) ;
1258+
11381259 if ( _connection . State == ConnectionState . Closed )
11391260 {
11401261 _connection . Open ( ) ;
@@ -1155,9 +1276,9 @@ private void SetupXEvent(string eventSpecification, string targetSpecification)
11551276 { eventSpecification }
11561277 { targetSpecification }
11571278 WITH (
1158- MAX_MEMORY=4096 KB ,
1279+ MAX_MEMORY=16 MB ,
11591280 EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
1160- MAX_DISPATCH_LATENCY=30 SECONDS,
1281+ MAX_DISPATCH_LATENCY={ MaxXEventsLatencyS } SECONDS,
11611282 MAX_EVENT_SIZE=0 KB,
11621283 MEMORY_PARTITION_MODE=NONE,
11631284 TRACK_CAUSALITY=ON,
0 commit comments