diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln b/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln index 55edb7319efb..b07fbb94b985 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln @@ -21,6 +21,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.DigitalTwins.Core.Perf", "perf\Azure.DigitalTwins.Core.Perf\Azure.DigitalTwins.Core.Perf.csproj", "{207EB40F-CEBA-4658-8D46-B5644D37F18C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Test.Perf", "..\..\..\common\Perf\Azure.Test.Perf\Azure.Test.Perf.csproj", "{15EF5B20-42F6-4280-BE8E-DAC973B572A7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{E685A401-AF9E-4196-975B-26C4A340256F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,10 +49,22 @@ Global {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Release|Any CPU.ActiveCfg = Release|Any CPU {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Release|Any CPU.Build.0 = Release|Any CPU + {207EB40F-CEBA-4658-8D46-B5644D37F18C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {207EB40F-CEBA-4658-8D46-B5644D37F18C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {207EB40F-CEBA-4658-8D46-B5644D37F18C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {207EB40F-CEBA-4658-8D46-B5644D37F18C}.Release|Any CPU.Build.0 = Release|Any CPU + {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1} = {E685A401-AF9E-4196-975B-26C4A340256F} + {15EF5B20-42F6-4280-BE8E-DAC973B572A7} = {E685A401-AF9E-4196-975B-26C4A340256F} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A97F4B90-2591-4689-B1F8-5F21FE6D6CAE} EndGlobalSection diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Azure.DigitalTwins.Core.Perf.csproj b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Azure.DigitalTwins.Core.Perf.csproj new file mode 100644 index 000000000000..f209d1c262a9 --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Azure.DigitalTwins.Core.Perf.csproj @@ -0,0 +1,24 @@ + + + Exe + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/AdtInstancePopulator.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/AdtInstancePopulator.cs new file mode 100644 index 000000000000..808fb6a646da --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/AdtInstancePopulator.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Azure.DigitalTwins.Core.Perf.Infrastructure +{ + internal class AdtInstancePopulator + { + private static readonly string s_dtdlDirectoryPath = Path.Combine(GetWorkingDirectory(), "Infrastructure"); + + private static readonly string s_modelsPath = Path.Combine(s_dtdlDirectoryPath, "Models"); + private static readonly string s_twinsPath = Path.Combine(s_dtdlDirectoryPath, "Twins"); + + private const string RoomModelFileName = "Room.json"; + private const string RoomTwinFileName = "RoomTwin.json"; + + public static async Task CreateRoomModelAsync(DigitalTwinsClient client) + { + try + { + await client.CreateModelsAsync(new List { GetRoomModel() }).ConfigureAwait(false); + } + catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.Conflict) + { + Console.WriteLine("Model already exists"); + } + } + + public static async Task> CreateRoomTwinsForTestIdAsync(DigitalTwinsClient client, string testId, long countOftwins) + { + List createdTwins = new List(); + + string batchTwinPrefix = $"room-{testId}-{Guid.NewGuid().ToString().Substring(0, 8)}"; + for (long i = 0; i < countOftwins; i++) + { + string twinId = $"{batchTwinPrefix}-{i}"; + createdTwins.Add(await client.CreateOrReplaceDigitalTwinAsync(twinId, GetRoomTwin(testId)).ConfigureAwait(false)); + } + + return createdTwins; + } + + public static string GetRoomModel() + { + return LoadFileFromPath(s_modelsPath, RoomModelFileName); + } + + public static BasicDigitalTwin GetRoomTwin(string testId) + { + string value = LoadFileFromPath(s_twinsPath, RoomTwinFileName).Replace("TEST_ID", testId); + return JsonSerializer.Deserialize(value); + } + + private static string GetWorkingDirectory() + { + string codeBase = Assembly.GetExecutingAssembly().Location; + var uri = new UriBuilder(codeBase); + string path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + + private static string LoadFileFromPath(string path, string fileName) + { + string[] allFilesPath = Directory.GetFiles(path, "*.json"); + try + { + string filePathOfInterest = allFilesPath.Where(s => Path.GetFileName(s) == fileName).FirstOrDefault(); + return File.ReadAllText(filePathOfInterest); + } + catch (Exception ex) + { + Console.WriteLine($"Error reading twin types from disk due to: {ex.Message}", ConsoleColor.Red); + Environment.Exit(0); + } + + return null; + } + } +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Models/Room.json b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Models/Room.json new file mode 100644 index 000000000000..afb857025035 --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Models/Room.json @@ -0,0 +1,13 @@ +{ + "@id": "dtmi:com:samples:Room;1", + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Room", + "contents": [ + { + "@type": "Property", + "name": "TestId", + "schema": "string" + } + ] +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/PerfTestEnvironment.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/PerfTestEnvironment.cs new file mode 100644 index 000000000000..bcea3f1d7b3e --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/PerfTestEnvironment.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; + +namespace Azure.DigitalTwins.Core.Perf +{ + /// + /// Represents the ambient environment in which the test suite is being run, offering access to information such as environment variables. + /// + internal sealed class PerfTestEnvironment : TestEnvironment + { + /// + /// The shared instance of the to be used during test runs. + /// + public static PerfTestEnvironment Instance { get; } = new PerfTestEnvironment(); + + /// + /// The Digital Twins instance endpoint to run the tests against. + /// + public string DigitalTwinsUrl => GetVariable("DIGITALTWINS_URL"); + + /// + /// The Microsoft tenant Id for the App registration. + /// + /// The Microsoft tenant Id for the App registration, read from the "DIGITALTWINS_TENANT_ID" environment variable. + public string DigitalTwinsTenantId => GetVariable("DIGITALTWINS_TENANT_ID"); + + /// + /// The App registration client Id used to authenticate against the instance. + /// + /// The App registration client Id used to authenticate against the instance, read from the "DIGITALTWINS_CLIENT_ID" environment variable. + public string DigitalTwinsClientId => GetVariable("DIGITALTWINS_CLIENT_ID"); + + /// + /// The App registration client secret. + /// + /// The App registration client secret, read from the "DIGITALTWINS_CLIENT_SECRET" environment variable. + public string DigitalTwinsClientSecret => GetVariable("DIGITALTWINS_CLIENT_SECRET"); + + /// + /// Initializes a new instance of the class. + /// + public PerfTestEnvironment() + { + } + } +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Twins/RoomTwin.json b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Twins/RoomTwin.json new file mode 100644 index 000000000000..76b66820176d --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Infrastructure/Twins/RoomTwin.json @@ -0,0 +1,6 @@ +{ + "$metadata": { + "$model": "dtmi:com:samples:Room;1" + }, + "TestId": "TEST_ID" +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Program.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Program.cs new file mode 100644 index 000000000000..1ad7284aaa19 --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Program.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Reflection; +using Azure.Test.Perf; + +await PerfProgram.Main(Assembly.GetEntryAssembly(), args); diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Scenarios/QueryDigitalTwins.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Scenarios/QueryDigitalTwins.cs new file mode 100644 index 000000000000..96625d51685c --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/Azure.DigitalTwins.Core.Perf/Scenarios/QueryDigitalTwins.cs @@ -0,0 +1,125 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.DigitalTwins.Core.Perf.Infrastructure; +using Azure.Identity; +using Azure.Test.Perf; +using NUnit.Framework; +using FluentAssertions; + +namespace Azure.DigitalTwins.Core.Perf.Scenarios +{ + /// + /// The performance test scenario focused on running queries against digital twins instances. + /// + /// + public sealed class QueryDigitalTwins : PerfTest + { + private readonly DigitalTwinsClient _digitalTwinsClient; + private readonly string _testId; + private readonly long _size; + private readonly TimeSpan _delayPeriod = TimeSpan.FromMinutes(1); + private List _createdTwins = new List(); + + public QueryDigitalTwins(SizeOptions options) : base(options) + { + _digitalTwinsClient = new DigitalTwinsClient( + new Uri(PerfTestEnvironment.Instance.DigitalTwinsUrl), + new ClientSecretCredential( + PerfTestEnvironment.Instance.DigitalTwinsTenantId, + PerfTestEnvironment.Instance.DigitalTwinsClientId, + PerfTestEnvironment.Instance.DigitalTwinsClientSecret)); + + _size = options.Size; + _testId = Guid.NewGuid().ToString().Substring(0, 8); + } + + public override async Task GlobalSetupAsync() + { + await base.GlobalSetupAsync(); + + // Global setup code that runs once at the beginning of test execution. + // Create the model globally so all tests can take advantage of it. + await AdtInstancePopulator.CreateRoomModelAsync(_digitalTwinsClient).ConfigureAwait(false); + } + + public override async Task SetupAsync() + { + await base.SetupAsync(); + _createdTwins = await AdtInstancePopulator.CreateRoomTwinsForTestIdAsync(_digitalTwinsClient, _testId, _size).ConfigureAwait(false); + + // Since it takes some time for the newly created twins to be included in the query result, we have to wait some time. + await Task.Delay(_delayPeriod); + } + + public override async Task CleanupAsync() + { + // Individual test-level cleanup code that runs for each instance of the test. + await base.CleanupAsync(); + + // We will delete all twins created by this test instance. + foreach (BasicDigitalTwin twin in _createdTwins) + { + await _digitalTwinsClient.DeleteDigitalTwinAsync(twin.Id).ConfigureAwait(false); + } + } + + public override async Task GlobalCleanupAsync() + { + // Global cleanup code that runs once at the end of test execution. + await base.GlobalCleanupAsync(); + + // List all the models and delete all of them. + AsyncPageable allModels = _digitalTwinsClient.GetModelsAsync(); + + await foreach (DigitalTwinsModelData model in allModels) + { + await _digitalTwinsClient.DeleteModelAsync(model.Id).ConfigureAwait(false); + } + } + + /// + /// Queries for all digital twins using . + /// + /// The token used to signal cancellation request. + public override void Run(CancellationToken cancellationToken) + { + Pageable result = _digitalTwinsClient + .Query($"SELECT * FROM DIGITALTWINS WHERE TestId = '{_testId}'", CancellationToken.None); + long resultCount = 0; + + foreach (BasicDigitalTwin a in result) + { + resultCount++; + } + +#if DEBUG + resultCount.Should().Be(_size); +#endif + } + + /// + /// Queries for all digital twins using . + /// + /// The token used to signal cancellation request. + public override async Task RunAsync(CancellationToken cancellationToken) + { + AsyncPageable result = _digitalTwinsClient + .QueryAsync($"SELECT * FROM DIGITALTWINS WHERE TestId = '{_testId}'", CancellationToken.None); + long resultCount = 0; + + await foreach (BasicDigitalTwin a in result) + { + resultCount++; + } + +#if DEBUG + resultCount.Should().Be(_size); +#endif + } + } +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/README.md b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/README.md new file mode 100644 index 000000000000..8daea36df71a --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/perf/README.md @@ -0,0 +1,43 @@ +# Azure Digital Twins performance tests + +The assets in this area comprise a set of performance tests for the [Azure DigitalTwins client library for .NET](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/digitaltwins/Azure.DigitalTwins.Core) and its associated ecosystem. The artifacts in this library are intended to be used primarily with the Azure SDK engineering system's testing infrastructure, but may also be run as stand-alone applications from the command-line. + +You can learn more about the project structure [here](https://github.com/Azure/azure-sdk-for-net/wiki/Writing-performance-tests-for-Client-libraries). +## Purpose +Performance Testing using performance framework, in general, allows you to test throughput and latency offered to the customers via the SDKs. + +Major Benefit: +- Performance Regressions are caught prior to release. Regressions can come in from new code changes that get merged between two releases, new dependencies that get introduced or old dependencies that are upgraded. + +The Digital Twins performance tests will be plugged into performance automation pipelines automatically and will run the tests regularly to scan for any performance issues that should be fixed before releasing the SDK. + +## Perf test scenarios + +### QueryDigitalTwins + +This scenario tests API calls to the DigitalTwins service to query for Twins and Relationships. +The `GlobalTestSetupAsync` method override will create a single model that is used for all instances of the parallel test runs. This method is only invoked once and will not be called during the parallel test run across all instances of the test. +The `SetupAsync` method override will create multiple Twins that is configurable using the input options. Each test will create Twins using a unique test Id and will only query that subset during each run. + +## Running the tests + +Build a performance test project +```bash +dotnet run -c Release -f --no-build -p -- [parameters needed for the test] +``` + +Run the executable output of a project +```bash +dotnet run -c Release -f --no-build -p -- [parameters needed for the test] +``` + +\ can be one of netcoreapp2.1, netcoreapp3.1, net461 or net5.0. Note the -- before any custom parameters to pass. This prevents dotnet from trying to handle any ambiguous command line switches. + +You should use the scenario test class names as the first parameter that is needed for running the test. + +## Contributing +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/DigitalTwinRelationshipTests.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/DigitalTwinRelationshipTests.cs index 8ca516e3ca87..0420811e4935 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/DigitalTwinRelationshipTests.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/DigitalTwinRelationshipTests.cs @@ -19,9 +19,9 @@ public class DigitalTwinRelationshipTests : E2eTestBase // Relationships list operation default max item count is 10. We create 31 to make sure we will get over 3 pages of response. // Ideally, service team would let us set max items per page when listing, but that isn't a feature yet - private const int bulkRelationshipCount = 31; + private const int BulkRelationshipCount = 31; - private const int defaultRelationshipPageSize = 10; + private const int DefaultRelationshipPageSize = 10; public DigitalTwinRelationshipTests(bool isAsync) : base(isAsync) @@ -140,7 +140,7 @@ await client AsyncPageable floorRelationships = client.GetRelationshipsAsync(floorTwinId); int numberOfFloorRelationships = 0; - await foreach (var relationship in floorRelationships) + await foreach (BasicRelationship relationship in floorRelationships) { ++numberOfFloorRelationships; } @@ -154,7 +154,7 @@ await client containsRelationshipId.Value.Id.Should().Be(floorContainsRoomRelationshipId); int numberOfRelationships = 0; - await foreach (var relationship in roomTwinRelationships) + await foreach (BasicRelationship relationship in roomTwinRelationships) { ++numberOfRelationships; } @@ -289,7 +289,7 @@ public async Task Relationships_PaginationWorks() string randomPostfix = "-" + GetRandom(); string floorToRoomRelationshipPrefix = "FloorToRoomRelationship-"; string roomToFloorRelationshipPrefix = "RoomToFloorRelationship-"; - for (int i = 0; i < bulkRelationshipCount; i++) + for (int i = 0; i < BulkRelationshipCount; i++) { var floorContainsRoomRelationshipId = $"{floorToRoomRelationshipPrefix}{i}{randomPostfix}"; @@ -303,7 +303,7 @@ await client } // For the sake of test simplicity, we'll just add multiple relationships from the same room to the same floor. - for (int i = 0; i < bulkRelationshipCount; i++) + for (int i = 0; i < BulkRelationshipCount; i++) { var roomContainedInFloorRelationshipId = $"{roomToFloorRelationshipPrefix}{i}{randomPostfix}"; @@ -325,7 +325,7 @@ await client incomingRelationshipPageCount++; if (incomingRelationshipPage.ContinuationToken != null) { - incomingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); + incomingRelationshipPage.Values.Count.Should().Be(DefaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); } } @@ -340,13 +340,13 @@ await client outgoingRelationshipPageCount++; if (outgoingRelationshipPage.ContinuationToken != null) { - outgoingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); + outgoingRelationshipPage.Values.Count.Should().Be(DefaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); } } outgoingRelationshipPageCount.Should().BeGreaterThan(1, "Expected more than one page of outgoing relationships"); - for (int i = 0; i < bulkRelationshipCount; i++) + for (int i = 0; i < BulkRelationshipCount; i++) { await client.DeleteRelationshipAsync(floorTwinId, $"{floorToRoomRelationshipPrefix}{i}{randomPostfix}").ConfigureAwait(false); await client.DeleteRelationshipAsync(roomTwinId, $"{roomToFloorRelationshipPrefix}{i}{randomPostfix}").ConfigureAwait(false); diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/E2eTestBase.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/E2eTestBase.cs index c6f6015574ac..9f662cb47c44 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/E2eTestBase.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/E2eTestBase.cs @@ -96,7 +96,7 @@ protected string GetRandom() // model and creating a digital twin that implements this model. The work around is to list the model(s) after // creating them in order to accommodate for that lag. Once service side investigates and comes up with a solution, // there is no need to list the models after creating them. - protected async Task CreateAndListModelsAsync(DigitalTwinsClient client, List lists) + protected static async Task CreateAndListModelsAsync(DigitalTwinsClient client, List lists) { await client.CreateModelsAsync(lists).ConfigureAwait(false); diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/TestObjectSerializer.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/TestObjectSerializer.cs index 01670f204430..49ce44e6f503 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/TestObjectSerializer.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/tests/TestObjectSerializer.cs @@ -17,7 +17,7 @@ namespace Azure.DigitalTwins.Core.Tests /// public class TestObjectSerializer : ObjectSerializer { - private static JsonObjectSerializer _serializer = new JsonObjectSerializer(); + private static readonly JsonObjectSerializer s_serializer = new JsonObjectSerializer(); // This field is used by the tests to confirm the function was called. public bool WasDeserializeCalled { get; set; } @@ -28,25 +28,25 @@ public class TestObjectSerializer : ObjectSerializer public override object Deserialize(Stream stream, Type returnType, CancellationToken cancellationToken) { WasDeserializeCalled = true; - return _serializer.Deserialize(stream, returnType, cancellationToken); + return s_serializer.Deserialize(stream, returnType, cancellationToken); } - public async override ValueTask DeserializeAsync(Stream stream, Type returnType, CancellationToken cancellationToken) + public override async ValueTask DeserializeAsync(Stream stream, Type returnType, CancellationToken cancellationToken) { WasDeserializeCalled = true; - return await _serializer.DeserializeAsync(stream, returnType, cancellationToken); + return await s_serializer.DeserializeAsync(stream, returnType, cancellationToken); } public override void Serialize(Stream stream, object value, Type inputType, CancellationToken cancellationToken) { WasSerializeCalled = true; - _serializer.Serialize(stream, value, inputType, cancellationToken); + s_serializer.Serialize(stream, value, inputType, cancellationToken); } - public async override ValueTask SerializeAsync(Stream stream, object value, Type inputType, CancellationToken cancellationToken) + public override async ValueTask SerializeAsync(Stream stream, object value, Type inputType, CancellationToken cancellationToken) { WasSerializeCalled = true; - await _serializer.SerializeAsync(stream, value, inputType, cancellationToken); + await s_serializer.SerializeAsync(stream, value, inputType, cancellationToken); } } }