diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs index ea844ce0db..331369f98c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs @@ -77,6 +77,7 @@ public override async Task Cleanup() } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct, true)] [DataRow(ConnectionMode.Gateway, true)] [DataRow(ConnectionMode.Direct, false)] @@ -87,6 +88,7 @@ public override async Task PointSuccessOperationsTest(ConnectionMode mode, bool } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task PointReadFailureOperationsTest(ConnectionMode mode) @@ -95,6 +97,7 @@ public override async Task PointReadFailureOperationsTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task StreamReadFailureOperationsTest(ConnectionMode mode) @@ -103,6 +106,7 @@ public override async Task StreamReadFailureOperationsTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task StreamOperationsTest(ConnectionMode mode) @@ -111,6 +115,7 @@ public override async Task StreamOperationsTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task BatchOperationsTest(ConnectionMode mode) @@ -119,6 +124,7 @@ public override async Task BatchOperationsTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task SingleOperationMultipleTimesTest(ConnectionMode mode) @@ -127,6 +133,7 @@ public override async Task SingleOperationMultipleTimesTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task QueryOperationSinglePartitionTest(ConnectionMode mode) @@ -135,6 +142,7 @@ public override async Task QueryOperationSinglePartitionTest(ConnectionMode mode } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task QueryMultiPageSinglePartitionOperationTest(ConnectionMode mode) @@ -143,6 +151,7 @@ public override async Task QueryMultiPageSinglePartitionOperationTest(Connection } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task QueryOperationCrossPartitionTest(ConnectionMode mode) @@ -151,6 +160,7 @@ public override async Task QueryOperationCrossPartitionTest(ConnectionMode mode) } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task QueryOperationMutiplePageCrossPartitionTest(ConnectionMode mode) @@ -159,6 +169,7 @@ public override async Task QueryOperationMutiplePageCrossPartitionTest(Connectio } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] [DataRow(ConnectionMode.Gateway)] public override async Task QueryOperationInvalidContinuationTokenTest(ConnectionMode mode) @@ -167,6 +178,7 @@ public override async Task QueryOperationInvalidContinuationTokenTest(Connection } [TestMethod] + [Timeout(300000)] [DataRow(ConnectionMode.Direct)] public override async Task CreateItemWithSubStatusCodeTest(ConnectionMode mode) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs index 2b847ef133..edd70b9bc3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs @@ -214,7 +214,7 @@ public async Task AvailabilityStrategyNoTriggerTest(bool isPreferredLocationsEmp .Build(), result: FaultInjectionResultBuilder.GetResultBuilder(FaultInjectionServerErrorType.ResponseDelay) - .WithDelay(TimeSpan.FromMilliseconds(200)) + .WithDelay(TimeSpan.FromMilliseconds(500)) .Build()) .WithDuration(TimeSpan.FromMinutes(90)) .Build(); @@ -243,7 +243,7 @@ public async Task AvailabilityStrategyNoTriggerTest(bool isPreferredLocationsEmp ConnectionMode = ConnectionMode.Direct, ApplicationPreferredRegions = isPreferredLocationsEmpty ? new List() : new List() { region1, region2 }, AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy( - threshold: TimeSpan.FromMilliseconds(150), + threshold: TimeSpan.FromMilliseconds(300), thresholdStep: TimeSpan.FromMilliseconds(50)), Serializer = this.cosmosSystemTextJsonSerializer }; @@ -585,7 +585,7 @@ public async Task AvailabilityStrategyAllFaultsTests(string operation, string co ConnectionMode = ConnectionMode.Direct, ApplicationPreferredRegions = isPreferredLocationsEmpty ? new List() :new List() { region1, region2 }, AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy( - threshold: TimeSpan.FromMilliseconds(100), + threshold: TimeSpan.FromMilliseconds(200), thresholdStep: TimeSpan.FromMilliseconds(50)), Serializer = this.cosmosSystemTextJsonSerializer }; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemIntegrationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemIntegrationTests.cs index 99a1381360..7eb69d925e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemIntegrationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemIntegrationTests.cs @@ -794,7 +794,7 @@ public async Task ReadItemAsync_WithCircuitBreakerEnabledAndSingleMasterAccountA await this.TryCreateItems(itemsList); //Must Ensure the data is replicated to all regions - await Task.Delay(3000); + await Task.Delay(5000); bool isRegion1Available = true; bool isRegion2Available = true; @@ -1055,7 +1055,7 @@ public async Task ReadItemAsync_WithCircuitBreakerDisabledAndSingleMasterAccount await this.TryCreateItems(itemsList); //Must Ensure the data is replicated to all regions - await Task.Delay(3000); + await Task.Delay(5000); int consecutiveFailureCount = 10; for (int attemptCount = 1; attemptCount <= consecutiveFailureCount; attemptCount++) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemThinClientTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemThinClientTests.cs index d34866a6ca..561f7e4665 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemThinClientTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemThinClientTests.cs @@ -1,26 +1,26 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; using System.Net; using System.Net.Http; - using System.Text.Json; + using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using global::Azure; using global::Azure.Core; using Microsoft.Azure.Cosmos.Fluent; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.MultiRegionSetupHelpers; - using TestObject = MultiRegionSetupHelpers.CosmosIntegrationTestObject; - + using Microsoft.VisualStudio.TestTools.UnitTesting; + using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.MultiRegionSetupHelpers; + using TestObject = MultiRegionSetupHelpers.CosmosIntegrationTestObject; + [TestClass] public class CosmosItemThinClientTests { @@ -31,12 +31,12 @@ public class CosmosItemThinClientTests private MultiRegionSetupHelpers.CosmosSystemTextJsonSerializer cosmosSystemTextJsonSerializer; private const int ItemCount = 100; - [TestInitialize] - public async Task TestInitAsync() - { - Environment.SetEnvironmentVariable(ConfigurationManager.ThinClientModeEnabled, "True"); + [TestInitialize] + public async Task TestInitAsync() + { + Environment.SetEnvironmentVariable(ConfigurationManager.ThinClientModeEnabled, "True"); this.connectionString = Environment.GetEnvironmentVariable("COSMOSDB_THINCLIENT"); - + if (string.IsNullOrEmpty(this.connectionString)) { Assert.Fail("Set environment variable COSMOSDB_THINCLIENT to run the tests"); @@ -115,50 +115,50 @@ private async Task> CreateItemsSafeAsync(IEnumerable - { + [TestMethod] + [TestCategory("ThinClient")] + public async Task RegionalDatabaseAccountNameIsEmptyInPayload() + { + byte[] capturedPayload = null; + Environment.SetEnvironmentVariable(ConfigurationManager.ThinClientModeEnabled, "True"); + + // Initialize the serializer locally + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = null, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + CosmosSystemTextJsonSerializer serializer = new CosmosSystemTextJsonSerializer(jsonSerializerOptions); + + CosmosClientBuilder builder = new CosmosClientBuilder(this.connectionString) + .WithConnectionModeGateway() + .WithCustomSerializer(serializer) + .WithSendingRequestEventArgs(async (sender, e) => + { if (e.HttpRequest.Version == new Version(2, 0)) { - if (e.HttpRequest.Content != null) - { - capturedPayload = await e.HttpRequest.Content.ReadAsByteArrayAsync(); + if (e.HttpRequest.Content != null) + { + capturedPayload = await e.HttpRequest.Content.ReadAsByteArrayAsync(); } - } - }); - - using CosmosClient client = builder.Build(); - string uniqueDbName = "TestRegional_" + Guid.NewGuid().ToString(); - Database database = await client.CreateDatabaseIfNotExistsAsync(uniqueDbName); - string uniqueContainerName = "TestRegionalContainer_" + Guid.NewGuid().ToString(); - Container container = await database.CreateContainerIfNotExistsAsync(uniqueContainerName, "/pk"); - - string pk = "pk_regional"; - TestObject testItem = this.GenerateItems(pk).First(); - - // Act - ItemResponse response = await container.CreateItemAsync(testItem, new PartitionKey(testItem.Pk)); - Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); - - // Assert + } + }); + + using CosmosClient client = builder.Build(); + string uniqueDbName = "TestRegional_" + Guid.NewGuid().ToString(); + Database database = await client.CreateDatabaseIfNotExistsAsync(uniqueDbName); + string uniqueContainerName = "TestRegionalContainer_" + Guid.NewGuid().ToString(); + Container container = await database.CreateContainerIfNotExistsAsync(uniqueContainerName, "/pk"); + + string pk = "pk_regional"; + TestObject testItem = this.GenerateItems(pk).First(); + + // Act + ItemResponse response = await container.CreateItemAsync(testItem, new PartitionKey(testItem.Pk)); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + + // Assert Assert.IsNotNull(capturedPayload, "The request payload was not captured."); @@ -166,22 +166,22 @@ public async Task RegionalDatabaseAccountNameIsEmptyInPayload() // For `regionalDatabaseAccountName`, which is a SmallString (type 0x02), this is // serialized as two bytes: 0x02 (type) and 0x00 (length). // This byte pair represents an empty string value in RNTBD’s small-string encoding. - byte[] emptyStringToken = { 0x02, 0x00 }; - - bool foundEmptyStringToken = false; - for (int i = 0; i <= capturedPayload.Length - emptyStringToken.Length; i++) - { - if (capturedPayload[i] == emptyStringToken[0] && capturedPayload[i + 1] == emptyStringToken[1]) - { - foundEmptyStringToken = true; - break; - } - } - - Assert.IsTrue(foundEmptyStringToken, "The RNTBD payload should contain a token representing an empty string for the regional account name."); - - // Cleanup - await database.DeleteAsync(); + byte[] emptyStringToken = { 0x02, 0x00 }; + + bool foundEmptyStringToken = false; + for (int i = 0; i <= capturedPayload.Length - emptyStringToken.Length; i++) + { + if (capturedPayload[i] == emptyStringToken[0] && capturedPayload[i + 1] == emptyStringToken[1]) + { + foundEmptyStringToken = true; + break; + } + } + + Assert.IsTrue(foundEmptyStringToken, "The RNTBD payload should contain a token representing an empty string for the regional account name."); + + // Cleanup + await database.DeleteAsync(); } [TestMethod] @@ -1113,247 +1113,247 @@ public async Task TransactionalBatchCreateItemsTest() } } - [TestMethod] - [TestCategory("ThinClient")] - public async Task TestThinClientQueryPlanWithOrderBy() - { - List items = new List(); - string commonPk = "pk_orderby_test_" + Guid.NewGuid().ToString(); - - try - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, Boolean.TrueString); - - for (int i = 0; i < 5; i++) - { - items.Add(new TestObject - { - Id = Guid.NewGuid().ToString(), - Pk = commonPk, - Other = $"Item_{i:D3}", - }); - } - - List createdItems = await this.CreateItemsSafeAsync(items); - Assert.AreEqual(5, createdItems.Count, "All items should be created"); - - // Execute ORDER BY query - this requires QueryPlan and EPK range conversion - string query = "SELECT * FROM c WHERE c.pk = @pk ORDER BY c.other DESC"; - QueryDefinition queryDef = new QueryDefinition(query).WithParameter("@pk", commonPk); - - FeedIterator iterator = this.container.GetItemQueryIterator(queryDef); - - List results = new List(); - int pageCount = 0; - - while (iterator.HasMoreResults) - { - FeedResponse response = await iterator.ReadNextAsync(); - results.AddRange(response); - pageCount++; - - string diagnostics = response.Diagnostics.ToString(); - Assert.IsTrue(diagnostics.Contains("|F4"), $"Page {pageCount}: Should use ThinClient"); - } - - Assert.AreEqual(5, results.Count, "Should return all 5 items"); - - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); - - foreach (TestObject item in items) - { - try - { - await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); - } - catch { } - } - } - } - - [TestMethod] - [TestCategory("ThinClient")] - public async Task TestThinClientQueryPlanCrossPartitionWithFilter() - { - List items = new List(); - string baseGuid = Guid.NewGuid().ToString(); - - try - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, "True"); - - string[] partitionKeys = { - $"pk_filter_1_{baseGuid}", - $"pk_filter_2_{baseGuid}", - $"pk_filter_3_{baseGuid}" - }; - - for (int pkIndex = 0; pkIndex < partitionKeys.Length; pkIndex++) - { - for (int i = 0; i < 3; i++) - { - items.Add(new TestObject - { - Id = Guid.NewGuid().ToString(), - Pk = partitionKeys[pkIndex], - Other = $"Value_{i}", - }); - } - } - - List createdItems = await this.CreateItemsSafeAsync(items); - Assert.AreEqual(9, createdItems.Count, "All 9 items should be created"); - - string query = "SELECT * FROM c ORDER BY c._ts"; - - FeedIterator iterator = this.container.GetItemQueryIterator(query); - - List results = new List(); - int pageCount = 0; - - while (iterator.HasMoreResults) - { - FeedResponse response = await iterator.ReadNextAsync(); - results.AddRange(response); - pageCount++; - - string diagnostics = response.Diagnostics.ToString(); - Assert.IsTrue(diagnostics.Contains("|F4"), $"Page {pageCount}: Should use ThinClient"); - } - - Assert.IsTrue(results.Count >= 9, - $"Should return at least 9 items, got {results.Count}"); - - int foundCount = 0; - foreach (TestObject item in createdItems) - { - if (results.Any(r => r.Id == item.Id)) - { - foundCount++; - } - } - - Assert.IsTrue(foundCount >= 9, - $"Should find all 9 test items in results, found {foundCount}"); - - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); - - foreach (TestObject item in items) - { - try - { - await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); - } - catch { } - } - } - } - - [TestMethod] - [TestCategory("ThinClient")] - public async Task TestThinClientQueryPlanMultiPartitionFanout() - { - List items = new List(); - string baseGuid = Guid.NewGuid().ToString(); - - try - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, Boolean.TrueString); - - // Create items across many distinct partition keys to ensure multi-partition fanout - int partitionCount = 10; - int itemsPerPartition = 3; - - for (int pkIndex = 0; pkIndex < partitionCount; pkIndex++) - { - string pk = $"pk_fanout_{pkIndex}_{baseGuid}"; - for (int i = 0; i < itemsPerPartition; i++) - { - items.Add(new TestObject - { - Id = Guid.NewGuid().ToString(), - Pk = pk, - Other = $"Partition_{pkIndex}_Item_{i}", - }); - } - } - - int totalExpected = partitionCount * itemsPerPartition; - List createdItems = await this.CreateItemsSafeAsync(items); - Assert.AreEqual(totalExpected, createdItems.Count, $"All {totalExpected} items should be created"); - - // Execute a cross-partition ORDER BY query (requires QueryPlan + fanout) - string query = "SELECT * FROM c WHERE STARTSWITH(c.other, 'Partition_') ORDER BY c.other ASC"; - - // Run query via ThinClient mode - FeedIterator thinClientIterator = this.container.GetItemQueryIterator(query); - - List thinClientResults = new List(); - while (thinClientIterator.HasMoreResults) - { - FeedResponse response = await thinClientIterator.ReadNextAsync(); - thinClientResults.AddRange(response); - - string diagnostics = response.Diagnostics.ToString(); - Assert.IsTrue(diagnostics.Contains("|F4"), "Should use ThinClient mode"); - } - - // Verify all items are returned - int foundCount = createdItems.Count(created => - thinClientResults.Any(r => r.Id == created.Id)); - Assert.AreEqual(totalExpected, foundCount, - $"Should find all {totalExpected} test items in fanout results, found {foundCount}"); - - // Compare with Gateway mode results to verify correctness - using CosmosClient gatewayClient = new CosmosClient( - this.connectionString, - new CosmosClientOptions() - { - ConnectionMode = ConnectionMode.Gateway, - Serializer = this.cosmosSystemTextJsonSerializer, - }); - - Container gatewayContainer = gatewayClient.GetContainer(this.database.Id, this.container.Id); - FeedIterator gatewayIterator = gatewayContainer.GetItemQueryIterator(query); - - List gatewayResults = new List(); - while (gatewayIterator.HasMoreResults) - { - FeedResponse response = await gatewayIterator.ReadNextAsync(); - gatewayResults.AddRange(response); - } - - // ThinClient and Gateway should return the same item count - Assert.AreEqual(gatewayResults.Count, thinClientResults.Count, - $"ThinClient ({thinClientResults.Count}) and Gateway ({gatewayResults.Count}) should return the same number of items."); - - // Verify both results contain the same item IDs - HashSet thinClientIds = new HashSet(thinClientResults.Select(r => r.Id)); - HashSet gatewayIds = new HashSet(gatewayResults.Select(r => r.Id)); - Assert.IsTrue(thinClientIds.SetEquals(gatewayIds), - "ThinClient and Gateway should return the same set of items."); - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); - - foreach (TestObject item in items) - { - try - { - await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); - } - catch { } - } - } - } - + [TestMethod] + [TestCategory("ThinClient")] + public async Task TestThinClientQueryPlanWithOrderBy() + { + List items = new List(); + string commonPk = "pk_orderby_test_" + Guid.NewGuid().ToString(); + + try + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, Boolean.TrueString); + + for (int i = 0; i < 5; i++) + { + items.Add(new TestObject + { + Id = Guid.NewGuid().ToString(), + Pk = commonPk, + Other = $"Item_{i:D3}", + }); + } + + List createdItems = await this.CreateItemsSafeAsync(items); + Assert.AreEqual(5, createdItems.Count, "All items should be created"); + + // Execute ORDER BY query - this requires QueryPlan and EPK range conversion + string query = "SELECT * FROM c WHERE c.pk = @pk ORDER BY c.other DESC"; + QueryDefinition queryDef = new QueryDefinition(query).WithParameter("@pk", commonPk); + + FeedIterator iterator = this.container.GetItemQueryIterator(queryDef); + + List results = new List(); + int pageCount = 0; + + while (iterator.HasMoreResults) + { + FeedResponse response = await iterator.ReadNextAsync(); + results.AddRange(response); + pageCount++; + + string diagnostics = response.Diagnostics.ToString(); + Assert.IsTrue(diagnostics.Contains("|F4"), $"Page {pageCount}: Should use ThinClient"); + } + + Assert.AreEqual(5, results.Count, "Should return all 5 items"); + + } + finally + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); + + foreach (TestObject item in items) + { + try + { + await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); + } + catch { } + } + } + } + + [TestMethod] + [TestCategory("ThinClient")] + public async Task TestThinClientQueryPlanCrossPartitionWithFilter() + { + List items = new List(); + string baseGuid = Guid.NewGuid().ToString(); + + try + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, "True"); + + string[] partitionKeys = { + $"pk_filter_1_{baseGuid}", + $"pk_filter_2_{baseGuid}", + $"pk_filter_3_{baseGuid}" + }; + + for (int pkIndex = 0; pkIndex < partitionKeys.Length; pkIndex++) + { + for (int i = 0; i < 3; i++) + { + items.Add(new TestObject + { + Id = Guid.NewGuid().ToString(), + Pk = partitionKeys[pkIndex], + Other = $"Value_{i}", + }); + } + } + + List createdItems = await this.CreateItemsSafeAsync(items); + Assert.AreEqual(9, createdItems.Count, "All 9 items should be created"); + + string query = "SELECT * FROM c ORDER BY c._ts"; + + FeedIterator iterator = this.container.GetItemQueryIterator(query); + + List results = new List(); + int pageCount = 0; + + while (iterator.HasMoreResults) + { + FeedResponse response = await iterator.ReadNextAsync(); + results.AddRange(response); + pageCount++; + + string diagnostics = response.Diagnostics.ToString(); + Assert.IsTrue(diagnostics.Contains("|F4"), $"Page {pageCount}: Should use ThinClient"); + } + + Assert.IsTrue(results.Count >= 9, + $"Should return at least 9 items, got {results.Count}"); + + int foundCount = 0; + foreach (TestObject item in createdItems) + { + if (results.Any(r => r.Id == item.Id)) + { + foundCount++; + } + } + + Assert.IsTrue(foundCount >= 9, + $"Should find all 9 test items in results, found {foundCount}"); + + } + finally + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); + + foreach (TestObject item in items) + { + try + { + await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); + } + catch { } + } + } + } + + [TestMethod] + [TestCategory("ThinClient")] + public async Task TestThinClientQueryPlanMultiPartitionFanout() + { + List items = new List(); + string baseGuid = Guid.NewGuid().ToString(); + + try + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, Boolean.TrueString); + + // Create items across many distinct partition keys to ensure multi-partition fanout + int partitionCount = 10; + int itemsPerPartition = 3; + + for (int pkIndex = 0; pkIndex < partitionCount; pkIndex++) + { + string pk = $"pk_fanout_{pkIndex}_{baseGuid}"; + for (int i = 0; i < itemsPerPartition; i++) + { + items.Add(new TestObject + { + Id = Guid.NewGuid().ToString(), + Pk = pk, + Other = $"Partition_{pkIndex}_Item_{i}", + }); + } + } + + int totalExpected = partitionCount * itemsPerPartition; + List createdItems = await this.CreateItemsSafeAsync(items); + Assert.AreEqual(totalExpected, createdItems.Count, $"All {totalExpected} items should be created"); + + // Execute a cross-partition ORDER BY query (requires QueryPlan + fanout) + string query = "SELECT * FROM c WHERE STARTSWITH(c.other, 'Partition_') ORDER BY c.other ASC"; + + // Run query via ThinClient mode + FeedIterator thinClientIterator = this.container.GetItemQueryIterator(query); + + List thinClientResults = new List(); + while (thinClientIterator.HasMoreResults) + { + FeedResponse response = await thinClientIterator.ReadNextAsync(); + thinClientResults.AddRange(response); + + string diagnostics = response.Diagnostics.ToString(); + Assert.IsTrue(diagnostics.Contains("|F4"), "Should use ThinClient mode"); + } + + // Verify all items are returned + int foundCount = createdItems.Count(created => + thinClientResults.Any(r => r.Id == created.Id)); + Assert.AreEqual(totalExpected, foundCount, + $"Should find all {totalExpected} test items in fanout results, found {foundCount}"); + + // Compare with Gateway mode results to verify correctness + using CosmosClient gatewayClient = new CosmosClient( + this.connectionString, + new CosmosClientOptions() + { + ConnectionMode = ConnectionMode.Gateway, + Serializer = this.cosmosSystemTextJsonSerializer, + }); + + Container gatewayContainer = gatewayClient.GetContainer(this.database.Id, this.container.Id); + FeedIterator gatewayIterator = gatewayContainer.GetItemQueryIterator(query); + + List gatewayResults = new List(); + while (gatewayIterator.HasMoreResults) + { + FeedResponse response = await gatewayIterator.ReadNextAsync(); + gatewayResults.AddRange(response); + } + + // ThinClient and Gateway should return the same item count + Assert.AreEqual(gatewayResults.Count, thinClientResults.Count, + $"ThinClient ({thinClientResults.Count}) and Gateway ({gatewayResults.Count}) should return the same number of items."); + + // Verify both results contain the same item IDs + HashSet thinClientIds = new HashSet(thinClientResults.Select(r => r.Id)); + HashSet gatewayIds = new HashSet(gatewayResults.Select(r => r.Id)); + Assert.IsTrue(thinClientIds.SetEquals(gatewayIds), + "ThinClient and Gateway should return the same set of items."); + } + finally + { + Environment.SetEnvironmentVariable(ConfigurationManager.BypassQueryParsing, null); + + foreach (TestObject item in items) + { + try + { + await this.container.DeleteItemAsync(item.Id, new PartitionKey(item.Pk)); + } + catch { } + } + } + } + /// /// DelegatingHandler that intercepts HTTP requests and can inject faults /// @@ -1378,5 +1378,5 @@ protected override Task SendAsync( return base.SendAsync(request, cancellationToken); } } - } + } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DistributedTransaction/DistributedTransactionE2ETests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DistributedTransaction/DistributedTransactionE2ETests.cs index b05f39288e..51c71dcbde 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DistributedTransaction/DistributedTransactionE2ETests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DistributedTransaction/DistributedTransactionE2ETests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using PartitionKey = Cosmos.PartitionKey; [TestClass] + [DoNotParallelize] public class DistributedTransactionE2ETests : BaseCosmosClientHelper { private const string IdempotencyTokenHeader = HttpConstants.HttpHeaders.IdempotencyToken; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 9e8e2250c1..4b35dd1f22 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -494,6 +494,7 @@ public async Task ChangeFeedAsync() [TestMethod] [TestCategory("Flaky")] + [Timeout(300000)] public async Task QueryAsync() { List inputs = new List(); @@ -818,6 +819,7 @@ public async Task ValidateInvalidCredentialsTraceAsync() [TestMethod] [TestCategory("Flaky")] + [Timeout(300000)] public async Task TypedPointOperationsAsync() { List inputs = new List(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/AvailabilityStrategyUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/AvailabilityStrategyUnitTests.cs index 8123290a06..e2d91daa3c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/AvailabilityStrategyUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/AvailabilityStrategyUnitTests.cs @@ -281,8 +281,8 @@ public async Task AppCancellationDuringHedging_DoesNotSpawnNewHedgeRequests() { // Arrange CrossRegionHedgingAvailabilityStrategy availabilityStrategy = new CrossRegionHedgingAvailabilityStrategy( - threshold: TimeSpan.FromMilliseconds(10), - thresholdStep: TimeSpan.FromMilliseconds(10)); + threshold: TimeSpan.FromMilliseconds(100), + thresholdStep: TimeSpan.FromMilliseconds(100)); using RequestMessage request = CreateReadRequest(); using CosmosClient mockCosmosClient = CreateMockClientWithRegions(3); @@ -296,19 +296,16 @@ public async Task AppCancellationDuringHedging_DoesNotSpawnNewHedgeRequests() if (callNumber == 1) { - // First request: cancel the app token after a brief delay + // First request: cancel the app token immediately // This simulates an e2e timeout scenario - _ = Task.Delay(15).ContinueWith(_ => appCts.Cancel()); + appCts.Cancel(); + } - // Then wait - this will be cancelled - try - { - await Task.Delay(TimeSpan.FromSeconds(30), ct); - } - catch (OperationCanceledException) - { - throw; - } + // All requests block deterministically until cancelled via the token + TaskCompletionSource tcs = new TaskCompletionSource(); + using (ct.Register(() => tcs.TrySetCanceled(ct))) + { + await tcs.Task; } return new ResponseMessage(HttpStatusCode.OK); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs index 2ee53dbdc2..9da46da620 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs @@ -150,9 +150,14 @@ public async Task ValidatesCongestionControlAsync() // 300 batch request should atleast sum up to 1000 ms barrier with wait time of 20ms in executor await Task.WhenAll(contexts); - await Task.Delay(2000); + // Poll for semaphore count to increase, with a reasonable timeout + System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); + while (newLimiter.CurrentCount < 2 && sw.Elapsed < TimeSpan.FromSeconds(10)) + { + await Task.Delay(200); + } - Assert.IsTrue(newLimiter.CurrentCount >= 2, "Count of threads that can enter into semaphore should increase atleast by 1"); + Assert.IsTrue(newLimiter.CurrentCount >= 2, $"Count of threads that can enter into semaphore should increase atleast by 1. Actual: {newLimiter.CurrentCount}"); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerTests.cs index 7da78abce1..7494e9e49d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerTests.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed.Tests { using System; + using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Exceptions; @@ -229,7 +230,22 @@ public async Task Controller_ShouldReleasesLease_IfObserverExits() .Returns(new PartitionSupervisorCore(this.lease, this.observer, this.partitionProcessor, this.leaseRenewer)); await this.sut.AddOrUpdateLeaseAsync(this.lease).ConfigureAwait(false); - await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false); + + // Poll for lease release with a bounded timeout instead of a fixed delay + Stopwatch sw = Stopwatch.StartNew(); + while (sw.Elapsed < TimeSpan.FromSeconds(5)) + { + try + { + Mock.Get(this.leaseManager) + .Verify(manager => manager.ReleaseAsync(It.IsAny()), Times.Once); + break; + } + catch (MockException) + { + await Task.Delay(50).ConfigureAwait(false); + } + } Mock.Get(this.leaseManager) .Verify(manager => manager.ReleaseAsync(It.IsAny()), Times.Once); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs index b98c339402..43437e1488 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs @@ -329,9 +329,11 @@ public async Task TestTokenCredentialBackgroundRefreshAsync() Assert.AreEqual(token1, t2); // Wait until the background refresh occurs. + Stopwatch sw = Stopwatch.StartNew(); while (testTokenCredential.NumTimesInvoked == 1) { - await Task.Delay(500); + Assert.IsTrue(sw.Elapsed < TimeSpan.FromSeconds(20), "Background token refresh did not occur within 20 seconds."); + await Task.Delay(200); } string t3 = await tokenCredentialCache.GetTokenAsync(NoOpTrace.Singleton); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosHttpClientCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosHttpClientCoreTests.cs index 05c521db3a..bcc2cc9d71 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosHttpClientCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosHttpClientCoreTests.cs @@ -57,6 +57,7 @@ static Task sendFunc(HttpRequestMessage request, Cancellati [TestMethod] [TestCategory("Flaky")] + [Timeout(120000)] public async Task RetryTransientIssuesTestAsync() { using CancellationTokenSource cancellationTokenSource1 = new CancellationTokenSource(); @@ -68,15 +69,15 @@ public async Task RetryTransientIssuesTestAsync() { {HttpTimeoutPolicyControlPlaneRead.Instance, new List() { - TimeSpan.FromSeconds(5.1), - TimeSpan.FromSeconds(10.1), - TimeSpan.FromSeconds(20.1) + TimeSpan.FromSeconds(6), + TimeSpan.FromSeconds(11), + TimeSpan.FromSeconds(21) }}, {HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance, new List() { - TimeSpan.FromSeconds(.6), - TimeSpan.FromSeconds(5.1), - TimeSpan.FromSeconds(65.1) + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(6), + TimeSpan.FromSeconds(66) }}, }; @@ -388,6 +389,7 @@ Task sendFunc(HttpRequestMessage request, CancellationToken [TestMethod] [TestCategory("Flaky")] + [Timeout(120000)] public async Task RetryTransientIssuesForQueryPlanTestAsync() { DocumentServiceRequest documentServiceRequest = DocumentServiceRequest.Create( @@ -411,7 +413,7 @@ async Task sendFunc(HttpRequestMessage request, Cancellatio if (count <= 2) { Assert.IsFalse(cancellationToken.IsCancellationRequested); - await Task.Delay(retry.Current.requestTimeout + TimeSpan.FromSeconds(.1)); + await Task.Delay(retry.Current.requestTimeout + TimeSpan.FromSeconds(1)); cancellationToken.ThrowIfCancellationRequested(); Assert.Fail("Cancellation token should be canceled"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs index 8e2947234e..c61aa1c756 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs @@ -29,6 +29,7 @@ public class GlobalEndpointManagerTest /// [TestMethod] [TestCategory("Flaky")] + [Timeout(30000)] public async Task EndpointFailureMockTest() { Environment.SetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS", "100"); @@ -94,11 +95,20 @@ public async Task EndpointFailureMockTest() Assert.AreEqual(globalEndpointManager.WriteEndpoints[0], globalEndpointManager.ReadEndpoints[0]); getAccountInfoCount = 0; - //Sleep 3 seconds for the unavailable endpoint entry to expire and background refresh timer to kick in - await Task.Delay(TimeSpan.FromSeconds(3)); + //Poll for the unavailable endpoint entry to expire and background refresh timer to kick in + Stopwatch sw = Stopwatch.StartNew(); + while (sw.Elapsed < TimeSpan.FromSeconds(10)) + { + await Task.Delay(200); + await globalEndpointManager.RefreshLocationAsync(); + if (globalEndpointManager.ReadEndpoints[0].Equals(new Uri(readLocation1.Endpoint))) + { + break; + } + } + Assert.IsTrue(getAccountInfoCount > 0, "Callback is not working. There should be at least one call in this time frame."); - await globalEndpointManager.RefreshLocationAsync(); Assert.AreEqual(new Uri(readLocation1.Endpoint), globalEndpointManager.ReadEndpoints[0], "Read endpoint did not switch back to location 1 after the unavailable entry expired."); }