From 423e103445ba00a1f5f2c5cd3d2a5f5ae590fbec Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 22 Aug 2024 01:23:43 +0700 Subject: [PATCH 01/20] Added net8.0 target and tests (fixes #94) --- .build/TestTargetFramework.props | 2 +- ...ish-test-results-for-target-frameworks.yml | 10 +++ .build/azure-templates/run-tests-on-os.yml | 78 +++++++++++++++++-- .build/dependencies.props | 4 +- .build/runbuild.ps1 | 2 +- Directory.Build.targets | 35 +++++---- azure-pipelines.yml | 54 ++++++++++++- .../J2N.TestFramework.csproj | 2 +- src/J2N/J2N.csproj | 2 +- tests/J2N.Tests/J2N.Tests.csproj | 2 +- 10 files changed, 161 insertions(+), 30 deletions(-) diff --git a/.build/TestTargetFramework.props b/.build/TestTargetFramework.props index f1f43746..ee77308b 100644 --- a/.build/TestTargetFramework.props +++ b/.build/TestTargetFramework.props @@ -26,7 +26,7 @@ net452 | net40 --> - net6.0;net5.0;net48;net472;net461;net452 + net8.0;net6.0;net5.0;net48;net472;net461;net452 diff --git a/.build/azure-templates/publish-test-results-for-target-frameworks.yml b/.build/azure-templates/publish-test-results-for-target-frameworks.yml index cfdf5dcc..04196a29 100644 --- a/.build/azure-templates/publish-test-results-for-target-frameworks.yml +++ b/.build/azure-templates/publish-test-results-for-target-frameworks.yml @@ -24,6 +24,16 @@ steps: EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName') displayName: 'Validate Template Parameters' +- template: publish-test-results.yml + parameters: + framework: 'net8.0' + testProjectName: '${{ parameters.testProjectName }}' + osName: '${{ parameters.osName }}' + testPlatform: '${{ parameters.testPlatform }}' + testResultsFormat: '${{ parameters.testResultsFormat }}' + testResultsArtifactName: '${{ parameters.testResultsArtifactName }}' + testResultsFileName: '${{ parameters.testResultsFileName }}' + - template: publish-test-results.yml parameters: framework: 'net6.0' diff --git a/.build/azure-templates/run-tests-on-os.yml b/.build/azure-templates/run-tests-on-os.yml index 9c3f5747..b3a33267 100644 --- a/.build/azure-templates/run-tests-on-os.yml +++ b/.build/azure-templates/run-tests-on-os.yml @@ -33,21 +33,87 @@ steps: EnsureNotNullOrEmpty('${{ parameters.dotNetSdkVersion }}', 'dotNetSdkVersion') displayName: 'Validate Template Parameters' +- pwsh: | + $testPlatform = '${{ parameters.vsTestPlatform }}' + if ($IsWindows -eq $null) { + $IsWindows = $env:OS.StartsWith('Win') + } + $performMulitLevelLookup = if ($IsWindows -and $testPlatform.Equals('x86')) { 'true' } else { 'false' } + Write-Host "##vso[task.setvariable variable=PerformMultiLevelLookup;]$performMulitLevelLookup" + +#- template: 'show-all-environment-variables.yml' # Uncomment for debugging + - template: 'install-dotnet-sdk.yml' parameters: sdkVersion: '${{ parameters.dotNetSdkVersion }}' + performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' + + # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not + # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. + # So, we install it afterward and set the environment variable so the above SDK can delegate to it. + # This code only works on Windows. +- pwsh: | + $sdkVersion = '${{ parameters.dotNetSdkVersion }}' + $architecture = '${{ parameters.vsTestPlatform }}' + $installScriptPath = "${env:AGENT_TEMPDIRECTORY}/dotnet-install.ps1" + $installScriptUrl = "https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest $installScriptUrl -OutFile $installScriptPath -TimeoutSec 60 + $installPath = "${env:ProgramFiles(x86)}/dotnet" + & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath + Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" + displayName: 'Use .NET SDK ${{ parameters.dotNetSdkVersion }} (x86)' + condition: and(succeeded(), contains('${{ parameters.framework }}', 'net8.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: UseDotNet@2 - displayName: 'Use .NET Core sdk 3.1.416' + displayName: 'Use .NET SDK 6.0.421' inputs: - version: 3.1.416 - condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'netcoreapp3.')) + packageType: 'sdk' + version: '6.0.421' + performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' + condition: and(succeeded(), contains('${{ parameters.framework }}', 'net6.')) + + # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not + # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. + # So, we install it afterward and set the environment variable so the above SDK can delegate to it. + # This code only works on Windows. +- pwsh: | + $sdkVersion = '6.0.421' + $architecture = '${{ parameters.vsTestPlatform }}' + $installScriptPath = "${env:AGENT_TEMPDIRECTORY}/dotnet-install.ps1" + $installScriptUrl = "https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest $installScriptUrl -OutFile $installScriptPath -TimeoutSec 60 + $installPath = "${env:ProgramFiles(x86)}/dotnet" + & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath + Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" + displayName: 'Use .NET SDK 6.0.421 (x86)' + condition: and(succeeded(), contains('${{ parameters.framework }}', 'net6.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: UseDotNet@2 - displayName: 'Use .NET sdk 5.0.404' + displayName: 'Use .NET SDK 5.0.408' inputs: - version: 5.0.404 - condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net5.')) + packageType: 'sdk' + version: '5.0.408' + performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' + condition: and(succeeded(), contains('${{ parameters.framework }}', 'net5.')) + + # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not + # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. + # So, we install it afterward and set the environment variable so the above SDK can delegate to it. + # This code only works on Windows. +- pwsh: | + $sdkVersion = '5.0.408' + $architecture = '${{ parameters.vsTestPlatform }}' + $installScriptPath = "${env:AGENT_TEMPDIRECTORY}/dotnet-install.ps1" + $installScriptUrl = "https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest $installScriptUrl -OutFile $installScriptPath -TimeoutSec 60 + $installPath = "${env:ProgramFiles(x86)}/dotnet" + & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath + Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" + displayName: 'Use .NET SDK 5.0.408 (x86)' + condition: and(succeeded(), contains('${{ parameters.framework }}', 'net5.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: DownloadBuildArtifacts@0 displayName: 'Download Build Artifacts: ${{ parameters.binaryArtifactName }}' diff --git a/.build/dependencies.props b/.build/dependencies.props index ac942ac1..458077c7 100644 --- a/.build/dependencies.props +++ b/.build/dependencies.props @@ -3,14 +3,14 @@ 60.1.0-alpha.354 4.4.0 1.0.2 - 16.1.1 + 17.11.0 1.1.1 $(MicrosoftSourceLinkAzureReposGitPackageReferenceVersion) [3.5.73-alpha] 4.0.0 2.0.3 3.10.1 - 3.13.0 + 4.6.0 2.7.8 4.5.1 4.5.5 diff --git a/.build/runbuild.ps1 b/.build/runbuild.ps1 index d2ec4103..01c473e6 100644 --- a/.build/runbuild.ps1 +++ b/.build/runbuild.ps1 @@ -17,7 +17,7 @@ properties { [string]$configuration = "Release" [string]$platform = "Any CPU" [bool]$backupFiles = $true - [string]$minimumSdkVersion = "6.0.100" + [string]$minimumSdkVersion = "8.0.100" #test parameters [string]$testPlatforms = "x64" diff --git a/Directory.Build.targets b/Directory.Build.targets index 74f2021b..8f38a03f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -8,8 +8,8 @@ $(NoWarn);IDE0057 - - + + $(DefineConstants);FEATURE_APPCONTEXT_BASEDIRECTORY $(DefineConstants);FEATURE_ARRAYEMPTY @@ -23,8 +23,8 @@ portable - - + + $(DefineConstants);FEATURE_ARRAY_CLEAR_ARRAY $(DefineConstants);FEATURE_RANDOM_NEXTINT64 @@ -34,8 +34,8 @@ - - + + $(DefineConstants);FEATURE_CULTUREINFO_PREDEFINEDONLY $(DefineConstants);FEATURE_HASHSET_MODIFY_CONTINUEENUMERATION @@ -44,16 +44,16 @@ - - + + $(DefineConstants);FEATURE_COLLECTIONSMARSHAL_ASSPAN_LIST $(DefineConstants);FEATURE_NUMERICBITOPERATIONS - - + + $(DefineConstants);FEATURE_BIGINTEGER_CTOR_READONLYSPAN $(DefineConstants);FEATURE_BUFFER_MEMORYCOPY @@ -79,7 +79,15 @@ - + + + + + $(DefineConstants);FEATURE_CLONEABLE + + + $(DefineConstants);FEATURE_SERIALIZABLE @@ -87,12 +95,9 @@ $(DefineConstants);FEATURE_SERIALIZABLE_EXCEPTIONS - - $(DefineConstants);FEATURE_CLONEABLE - + $(DefineConstants);FEATURE_THREADABORT diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 743da9af..03ce9c1e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,9 +23,9 @@ name: 'vNext$(rev:.r)' # Format for build number (will be overridden) variables: - name: TestTargetFrameworks - value: 'net6.0;net5.0;net48;net472;net461;net452' + value: 'net8.0;net6.0;net5.0;net48;net472;net461;net452' - name: DotNetSDKVersion - value: '6.0.101' + value: '8.0.401' - name: BinaryArtifactName value: 'testbinaries' - name: NuGetArtifactName @@ -112,6 +112,56 @@ stages: displayName: 'Test Stage:' jobs: + - job: Test_net8_0_x64 + condition: and(succeeded(), ne(variables['RunTests'], 'false')) + strategy: + matrix: + Windows: + osName: 'Windows' + imageName: 'windows-latest' + maximumAllowedFailures: 0 # Maximum allowed failures for a successful build + Linux: + osName: 'Linux' + imageName: 'ubuntu-latest' + maximumAllowedFailures: 0 # Maximum allowed failures for a successful build + macOS: + osName: 'macOS' + imageName: 'macOS-latest' + maximumAllowedFailures: 0 # Maximum allowed failures for a successful build + displayName: 'Test net8.0,x64 on' + pool: + vmImage: $(imageName) + steps: + - template: '.build/azure-templates/run-tests-on-os.yml' + parameters: + osName: $(osName) + testTargetFrameworks: 'net8.0' + vsTestPlatform: 'x64' + testResultsArtifactName: '$(TestResultsArtifactName)' + maximumAllowedFailures: $(maximumAllowedFailures) + dotNetSdkVersion: '$(DotNetSDKVersion)' + + - job: Test_net8_0_x86 # Only run if explicitly enabled with RunX86Tests + condition: and(succeeded(), ne(variables['RunTests'], 'false'), eq(variables['RunX86Tests'], 'true')) + strategy: + matrix: + Windows: + osName: 'Windows' + imageName: 'windows-latest' + maximumAllowedFailures: 0 # Maximum allowed failures for a successful build + displayName: 'Test net8.0,x86 on' + pool: + vmImage: $(imageName) + steps: + - template: '.build/azure-templates/run-tests-on-os.yml' + parameters: + osName: $(osName) + testTargetFrameworks: 'net8.0' + vsTestPlatform: 'x86' + testResultsArtifactName: '$(TestResultsArtifactName)' + maximumAllowedFailures: $(maximumAllowedFailures) + dotNetSdkVersion: '$(DotNetSDKVersion)' + - job: Test_net6_0_x64 condition: and(succeeded(), ne(variables['RunTests'], 'false')) strategy: diff --git a/src/J2N.TestFramework/J2N.TestFramework.csproj b/src/J2N.TestFramework/J2N.TestFramework.csproj index 81e92bf3..36225204 100644 --- a/src/J2N.TestFramework/J2N.TestFramework.csproj +++ b/src/J2N.TestFramework/J2N.TestFramework.csproj @@ -2,7 +2,7 @@ - net6.0;netstandard2.1;netstandard2.0;net462;net45;net40 + net8.0;net6.0;netstandard2.1;netstandard2.0;net462;net45;net40 J2N false diff --git a/src/J2N/J2N.csproj b/src/J2N/J2N.csproj index a8c4055b..ec94bf0a 100644 --- a/src/J2N/J2N.csproj +++ b/src/J2N/J2N.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.1;netstandard2.0;net462;net45;net40 + net8.0;net6.0;netstandard2.1;netstandard2.0;net462;net45;net40 true diff --git a/tests/J2N.Tests/J2N.Tests.csproj b/tests/J2N.Tests/J2N.Tests.csproj index a9378388..8c5ee760 100644 --- a/tests/J2N.Tests/J2N.Tests.csproj +++ b/tests/J2N.Tests/J2N.Tests.csproj @@ -39,7 +39,7 @@ Version="$(NetFxSystemMemoryPackageReferenceVersion)" /> - + From cc224e7b5f216c9803db9898c6a44e9a659faf74 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 22 Aug 2024 01:39:31 +0700 Subject: [PATCH 02/20] azure-pipelines.yml + build-pack-and-publish-libraries.yml + run-tests-on-os.yml: Use pipeline artifacts for NuGet and binary artifacts to improve performance --- .../azure-templates/build-pack-and-publish-libraries.yml | 7 ++++--- .build/azure-templates/run-tests-on-os.yml | 6 +++--- azure-pipelines.yml | 7 ++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.build/azure-templates/build-pack-and-publish-libraries.yml b/.build/azure-templates/build-pack-and-publish-libraries.yml index 1c6cbe1b..768682c3 100644 --- a/.build/azure-templates/build-pack-and-publish-libraries.yml +++ b/.build/azure-templates/build-pack-and-publish-libraries.yml @@ -72,11 +72,12 @@ steps: Contents: '**/bin/${{ parameters.buildConfiguration }}/**/*.pdb' TargetFolder: '$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}' -- task: PublishBuildArtifacts@1 +- task: PublishPipelineArtifact@1 displayName: 'Publish Artifact: ${{ parameters.nugetArtifactName }}' inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}' - ArtifactName: '${{ parameters.nugetArtifactName }}' + targetPath: '$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}' + artifact: '${{ parameters.nugetArtifactName }}' + publishLocation: 'pipeline' condition: succeededOrFailed() # Loops through each framework in the TestTargetFrameworks variable and diff --git a/.build/azure-templates/run-tests-on-os.yml b/.build/azure-templates/run-tests-on-os.yml index b3a33267..09aa8273 100644 --- a/.build/azure-templates/run-tests-on-os.yml +++ b/.build/azure-templates/run-tests-on-os.yml @@ -115,11 +115,11 @@ steps: displayName: 'Use .NET SDK 5.0.408 (x86)' condition: and(succeeded(), contains('${{ parameters.framework }}', 'net5.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) -- task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts: ${{ parameters.binaryArtifactName }}' +- task: DownloadPipelineArtifact@2 + displayName: 'Download Pipeline Artifacts: ${{ parameters.binaryArtifactName }}' inputs: artifactName: ${{ parameters.binaryArtifactName }} - downloadPath: '$(System.DefaultWorkingDirectory)' + targetPath: '$(System.DefaultWorkingDirectory)' - pwsh: | $testTargetFrameworksString = '${{ parameters.testTargetFrameworks }}' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 03ce9c1e..6259e363 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -101,11 +101,12 @@ stages: nugetArtifactName: '$(NuGetArtifactName)' binaryArtifactName: '$(BinaryArtifactName)' - - task: PublishBuildArtifacts@1 + - task: PublishPipelineArtifact@1 displayName: 'Publish Artifact: $(BinaryArtifactName)' inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(BinaryArtifactName)' - ArtifactName: '$(BinaryArtifactName)' + targetPath: '$(Build.ArtifactStagingDirectory)/$(BinaryArtifactName)' + artifact: '$(BinaryArtifactName)' + publishLocation: 'pipeline' condition: succeededOrFailed() - stage: Test_Stage From f756eeb7a035c4399bef49da336af48e3d80c83e Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 22 Aug 2024 01:45:45 +0700 Subject: [PATCH 03/20] publish-test-results.yml: Added additional crash detection messages --- .../azure-templates/publish-test-results.yml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.build/azure-templates/publish-test-results.yml b/.build/azure-templates/publish-test-results.yml index 697142b0..fe54b690 100644 --- a/.build/azure-templates/publish-test-results.yml +++ b/.build/azure-templates/publish-test-results.yml @@ -67,12 +67,20 @@ steps: if ($reader.Name -eq 'RunInfos') { $inRunInfos = $true } - if ($inRunInfos -and !$crashed -and $reader.Name -eq 'Text' -and $reader.ReadInnerXml().Contains('Test host process crashed')) { - Write-Host "##vso[task.setvariable variable=HostCrashed;]true" - # Report all of the test projects that crashed - $crashedRuns = "$env:CRASHEDRUNS,$testProjectName".TrimStart(',') - Write-Host "##vso[task.setvariable variable=CrashedRuns;]$crashedRuns" - $crashed = $true + if ($inRunInfos -and !$crashed -and $reader.Name -eq 'Text') { + $innerXml = $reader.ReadInnerXml() + # Test for specific error messages - we may need to adjust this, as needed + if ($innerXml -and ($innerXml.Contains('Test host process crashed') ` + -or $innerXml.Contains('Could not load file or assembly') ` + -or $innerXml.Contains("Could not find `'dotnet.exe`' host") ` + -or $innerXml.Contains('No test is available') ` + -or $innerXml.Contains('exited with error'))) { + Write-Host "##vso[task.setvariable variable=HostCrashed;]true" + # Report all of the test projects that crashed + $crashedRuns = "$env:CRASHEDRUNS,$testProjectName".TrimStart(',') + Write-Host "##vso[task.setvariable variable=CrashedRuns;]$crashedRuns" + $crashed = $true + } } } if ($reader.NodeType -eq [System.Xml.XmlNodeType]::EndElement -and $reader.Name -eq 'RunInfos') { From f5ab082b13fa4b6541399f1b9275b35a72ad1748 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 22 Aug 2024 23:38:06 +0700 Subject: [PATCH 04/20] Directory.Build.targets: Switched to using FEATURE_UNICODE_DEFINED for specific characters, since some tests depend on them not being defined and need to change for versions of Unicode that define them. Eliminated FEATURE_ICU. --- Directory.Build.targets | 9 ++++++++- tests/J2N.Tests/TestCharacter.cs | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 8f38a03f..fea53fdc 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -23,6 +23,13 @@ portable + + + + $(DefineConstants);FEATURE_UNICODE_DEFINED_0x9FFF + + + @@ -39,8 +46,8 @@ $(DefineConstants);FEATURE_CULTUREINFO_PREDEFINEDONLY $(DefineConstants);FEATURE_HASHSET_MODIFY_CONTINUEENUMERATION - $(DefineConstants);FEATURE_ICU $(DefineConstants);FEATURE_MEMORYMARSHAL_GETARRAYDATAREFERENCE + $(DefineConstants);FEATURE_UNICODE_DEFINED_0x30000 diff --git a/tests/J2N.Tests/TestCharacter.cs b/tests/J2N.Tests/TestCharacter.cs index c01ab8ce..52e89c1a 100644 --- a/tests/J2N.Tests/TestCharacter.cs +++ b/tests/J2N.Tests/TestCharacter.cs @@ -1989,8 +1989,12 @@ public void Test_getType_I() assertTrue(Character.GetType((int)'$') == UnicodeCategory.CurrencySymbol); assertTrue(Character.GetType((int)'\u2029') == UnicodeCategory.ParagraphSeparator); +#if FEATURE_UNICODE_DEFINED_0x9FFF + assertTrue(Character.GetType(0x9FFF) == UnicodeCategory.OtherLetter); +#else assertTrue(Character.GetType(0x9FFF) == UnicodeCategory.OtherNotAssigned); -#if FEATURE_ICU +#endif +#if FEATURE_UNICODE_DEFINED_0x30000 assertTrue(Character.GetType(0x30000) == UnicodeCategory.OtherLetter); // This character is now defined in .NET 5 #else assertTrue(Character.GetType(0x30000) == UnicodeCategory.OtherNotAssigned); @@ -2114,7 +2118,7 @@ public void Test_isDefined_I() assertTrue(Character.IsDefined((int)'\u6039')); assertTrue(Character.IsDefined(0x10300)); -#if FEATURE_ICU +#if FEATURE_UNICODE_DEFINED_0x30000 assertTrue(Character.IsDefined(0x30000)); // This character is now defined in .NET 5 #else assertFalse(Character.IsDefined(0x30000)); From af536fc05d93223da5ead56eb3e4328615920d70 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Fri, 23 Aug 2024 00:09:33 +0700 Subject: [PATCH 05/20] J2N.Threading.Atomic (AtomicReference + AtomicReferenceArray): Use Volatile.Read() where supported rather than using a sentinel Comparand that is created using FormatterServices.GetUninitializedObject(), which is now obsolete. --- Directory.Build.targets | 1 + src/J2N/Threading/Atomic/AtomicReference.cs | 6 ++++++ src/J2N/Threading/Atomic/AtomicReferenceArray.cs | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/Directory.Build.targets b/Directory.Build.targets index fea53fdc..2d26cdf3 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -146,6 +146,7 @@ $(DefineConstants);FEATURE_METHODIMPLOPTIONS_AGRESSIVEINLINING $(DefineConstants);FEATURE_THREAD_ISENTERED $(DefineConstants);FEATURE_TYPEINFO + $(DefineConstants);FEATURE_VOLATILE diff --git a/src/J2N/Threading/Atomic/AtomicReference.cs b/src/J2N/Threading/Atomic/AtomicReference.cs index 28b7af20..fc22c793 100644 --- a/src/J2N/Threading/Atomic/AtomicReference.cs +++ b/src/J2N/Threading/Atomic/AtomicReference.cs @@ -35,7 +35,9 @@ namespace J2N.Threading.Atomic public class AtomicReference where T : class { private T? value; +#if !FEATURE_VOLATILE private static readonly T Comparand = (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor +#endif /// /// Creates a new with the given initial . @@ -66,7 +68,11 @@ public AtomicReference() /// public T? Value { +#if FEATURE_VOLATILE + get => Volatile.Read(ref value); +#else get => Interlocked.CompareExchange(ref value, Comparand, Comparand); +#endif set => Interlocked.Exchange(ref this.value, value); } diff --git a/src/J2N/Threading/Atomic/AtomicReferenceArray.cs b/src/J2N/Threading/Atomic/AtomicReferenceArray.cs index 81cd26ae..ddc47318 100644 --- a/src/J2N/Threading/Atomic/AtomicReferenceArray.cs +++ b/src/J2N/Threading/Atomic/AtomicReferenceArray.cs @@ -36,7 +36,9 @@ namespace J2N.Threading.Atomic public class AtomicReferenceArray : IStructuralFormattable where T : class { private readonly T?[] array; +#if !FEATURE_VOLATILE private static readonly T Comparand = (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor +#endif /// /// Creates a new of given . @@ -78,7 +80,11 @@ public AtomicReferenceArray(T?[] array) /// The current value. public T? this[int index] { +#if FEATURE_VOLATILE + get => Volatile.Read(ref array[index]); +#else get => Interlocked.CompareExchange(ref array[index], Comparand, Comparand); +#endif set => Interlocked.Exchange(ref array[index], value); } From fb00d62fbfb97eec55beaf057230347aff6cd3c8 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 05:29:35 +0700 Subject: [PATCH 06/20] J2N.csproj: Removed so we can inherit the version of the Directory.Build.props file --- src/J2N/J2N.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/J2N/J2N.csproj b/src/J2N/J2N.csproj index ec94bf0a..46c8e73a 100644 --- a/src/J2N/J2N.csproj +++ b/src/J2N/J2N.csproj @@ -8,7 +8,6 @@ NU1605;1591 enable - 9.0 From 7a7b75d3c6a5d5b6e999429cf97b62f8436221b3 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 12:35:56 +0700 Subject: [PATCH 07/20] tests/Directory.Build.targets: Added task to update System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization to true so we can run the binary serialization tests. This has no impact on how binary serialization is handled in production. --- tests/Directory.Build.targets | 77 ++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 5701f158..fc7550e9 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -1,4 +1,4 @@ - + @@ -33,4 +33,79 @@ $(NoWarn);IDE1006 + + + + + + + + + + + + + + + + + + + $(TargetDir)$(AssemblyName).runtimeconfig.json + false + true + + + + + + \ No newline at end of file From d5dc53ae422003fd7bea9d452872b97811699415 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 12:46:41 +0700 Subject: [PATCH 08/20] SWEEP: Marked all binary serialization methods obsolete and suppressed visibility from the IDE. Binary serialization is disabled by default in .NET 8, so we are preparing users for the eventual removal of these features. They still exist and are fully functional, though. --- Directory.Build.targets | 13 ++++--------- src/J2N.TestFramework/J2N.TestFramework.csproj | 2 ++ src/J2N/Collections/Concurrent/LurchTable.cs | 7 +++++-- src/J2N/Collections/Generic/Dictionary.cs | 5 +++++ src/J2N/Collections/Generic/HashSet.cs | 5 +++++ src/J2N/Collections/Generic/LinkedDictionary.cs | 5 +++++ src/J2N/Collections/Generic/LinkedHashSet.cs | 6 ++++++ src/J2N/Collections/Generic/List.cs | 9 ++++++++- .../Generic/NonRandomizedStringEqualityComparer.cs | 2 +- src/J2N/Collections/Generic/SortedDictionary.cs | 2 ++ src/J2N/Collections/Generic/SortedSet.TreeSubSet.cs | 6 ++++++ src/J2N/Collections/Generic/SortedSet.cs | 10 ++++++++++ src/J2N/IO/BufferOverflowException.cs | 2 ++ src/J2N/IO/BufferUnderflowException.cs | 2 ++ src/J2N/IO/InvalidMarkException.cs | 2 ++ src/J2N/IO/ReadOnlyBufferException.cs | 2 ++ src/J2N/Text/ParseException.cs | 7 +++++++ .../Generic/Dictionary/Dictionary.Generic.Tests.cs | 4 ++++ tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj | 2 ++ tests/J2N.Tests/J2N.Tests.csproj | 2 ++ 20 files changed, 82 insertions(+), 13 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 2d26cdf3..c5698d8e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -89,19 +89,14 @@ - - $(DefineConstants);FEATURE_CLONEABLE - - - - - $(DefineConstants);FEATURE_SERIALIZABLE - + $(DefineConstants);FEATURE_SERIALIZABLE_EXCEPTIONS + + $(DefineConstants);FEATURE_CLONEABLE diff --git a/src/J2N.TestFramework/J2N.TestFramework.csproj b/src/J2N.TestFramework/J2N.TestFramework.csproj index 36225204..28748fc2 100644 --- a/src/J2N.TestFramework/J2N.TestFramework.csproj +++ b/src/J2N.TestFramework/J2N.TestFramework.csproj @@ -18,6 +18,8 @@ $(NoWarn);IDE1006 $(NoWarn);SYSLIB0011 + $(NoWarn);SYSLIB0050 + $(NoWarn);SYSLIB0051 diff --git a/src/J2N/Collections/Concurrent/LurchTable.cs b/src/J2N/Collections/Concurrent/LurchTable.cs index 885d49cf..c6c5c62a 100644 --- a/src/J2N/Collections/Concurrent/LurchTable.cs +++ b/src/J2N/Collections/Concurrent/LurchTable.cs @@ -18,6 +18,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -2927,15 +2928,17 @@ public bool UpdateValue([AllowNull] TKey key, [AllowNull] ref TValue value) /// Exception class: LurchTableCorruptionException /// The LurchTable internal datastructure appears to be corrupted. /// -#if FEATURE_SERIALIZABLE +#if FEATURE_SERIALIZABLE_EXCEPTIONS [Serializable] #endif public class LurchTableCorruptionException : Exception { -#if FEATURE_SERIALIZABLE +#if FEATURE_SERIALIZABLE_EXCEPTIONS /// /// Serialization constructor /// + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected LurchTableCorruptionException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/src/J2N/Collections/Generic/Dictionary.cs b/src/J2N/Collections/Generic/Dictionary.cs index 8d0a090a..01622561 100644 --- a/src/J2N/Collections/Generic/Dictionary.cs +++ b/src/J2N/Collections/Generic/Dictionary.cs @@ -8,6 +8,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -454,6 +455,8 @@ private void AddRange(IEnumerable> enumerable) /// For more information, see /// XML and SOAP Serialization. /// + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected Dictionary(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { // We can't do anything with the keys and values until the entire graph has been deserialized @@ -659,6 +662,8 @@ public int EnsureCapacity(int capacity) /// is null. /// This method is an O(n) operation, where n is . [System.Security.SecurityCritical] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { // Customized serialization for Dictionary. diff --git a/src/J2N/Collections/Generic/HashSet.cs b/src/J2N/Collections/Generic/HashSet.cs index ea1fc803..f1c7bbd4 100644 --- a/src/J2N/Collections/Generic/HashSet.cs +++ b/src/J2N/Collections/Generic/HashSet.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -285,6 +286,8 @@ public HashSet(int capacity, IEqualityComparer? comparer) /// the information required to serialize the object. /// A structure that contains /// the source and destination of the serialized stream associated with the object. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected HashSet(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { // We can't do anything with the keys and values until the entire graph has been @@ -699,6 +702,8 @@ IEnumerator IEnumerable.GetEnumerator() /// Associated enumeration: /// [System.Security.SecurityCritical] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { if (info == null) diff --git a/src/J2N/Collections/Generic/LinkedDictionary.cs b/src/J2N/Collections/Generic/LinkedDictionary.cs index 64132242..95bdbd4f 100644 --- a/src/J2N/Collections/Generic/LinkedDictionary.cs +++ b/src/J2N/Collections/Generic/LinkedDictionary.cs @@ -30,6 +30,7 @@ THE SOFTWARE. using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -189,6 +190,8 @@ public LinkedDictionary(IEnumerable> collection, IEqu /// For more information, see /// XML and SOAP Serialization. /// + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected LinkedDictionary(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { siInfo = info; @@ -340,6 +343,8 @@ public int EnsureCapacity(int capacity) /// is null. /// This method is an O(n) operation, where n is . [System.Security.SecurityCritical] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { // Customized serialization for LinkedDictionary diff --git a/src/J2N/Collections/Generic/LinkedHashSet.cs b/src/J2N/Collections/Generic/LinkedHashSet.cs index 8c82903a..00ff3a10 100644 --- a/src/J2N/Collections/Generic/LinkedHashSet.cs +++ b/src/J2N/Collections/Generic/LinkedHashSet.cs @@ -21,6 +21,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using SCG = System.Collections.Generic; @@ -174,6 +175,8 @@ public LinkedHashSet(IEnumerable collection, IEqualityComparer? comparer) /// the information required to serialize the object. /// A structure that contains /// the source and destination of the serialized stream associated with the object. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected LinkedHashSet(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { hashSet = new HashSetWrapper(info, context); @@ -206,6 +209,7 @@ public ReadOnlySet AsReadOnly() [Serializable] private class HashSetWrapper : HashSet { + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] public HashSetWrapper(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } #endif @@ -314,6 +318,8 @@ public int EnsureCapacity(int capacity) /// Associated enumeration: /// [System.Security.SecurityCritical] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) => hashSet.GetObjectData(info, context); diff --git a/src/J2N/Collections/Generic/List.cs b/src/J2N/Collections/Generic/List.cs index e20b9fdc..a35a9f77 100644 --- a/src/J2N/Collections/Generic/List.cs +++ b/src/J2N/Collections/Generic/List.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -162,6 +163,8 @@ public List(int capacity) /// /// This constructor is called during deserialization to reconstitute an object that is transmitted over a stream. /// + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected List(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { siInfo = info; @@ -2378,11 +2381,13 @@ public bool TrueForAll(Predicate match) return true; } -#endregion + #endregion #region Custom Serialization #if FEATURE_SERIALIZABLE + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) => GetObjectData(info, context); /// @@ -2395,6 +2400,8 @@ public bool TrueForAll(Predicate match) /// of the serialized stream associated with the object. /// is null. /// Calling this method is an O(n) operation, where n is . + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { if (info == null) diff --git a/src/J2N/Collections/Generic/NonRandomizedStringEqualityComparer.cs b/src/J2N/Collections/Generic/NonRandomizedStringEqualityComparer.cs index f4499088..6780b9d9 100644 --- a/src/J2N/Collections/Generic/NonRandomizedStringEqualityComparer.cs +++ b/src/J2N/Collections/Generic/NonRandomizedStringEqualityComparer.cs @@ -44,7 +44,7 @@ private NonRandomizedStringEqualityComparer(IEqualityComparer underlyin } // This is used by the serialization engine. - //[Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] [EditorBrowsable(EditorBrowsableState.Never)] protected NonRandomizedStringEqualityComparer(SerializationInfo information, StreamingContext context) : this(EqualityComparer.Default) diff --git a/src/J2N/Collections/Generic/SortedDictionary.cs b/src/J2N/Collections/Generic/SortedDictionary.cs index 386f2690..fa95db2d 100644 --- a/src/J2N/Collections/Generic/SortedDictionary.cs +++ b/src/J2N/Collections/Generic/SortedDictionary.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; #if FEATURE_SERIALIZABLE @@ -1946,6 +1947,7 @@ public TreeSet() public TreeSet(IComparer comparer) : base(comparer) { } #if FEATURE_SERIALIZABLE + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private TreeSet(SerializationInfo siInfo, StreamingContext context) : base(siInfo, context) { } #endif diff --git a/src/J2N/Collections/Generic/SortedSet.TreeSubSet.cs b/src/J2N/Collections/Generic/SortedSet.TreeSubSet.cs index d2e37ddd..09e9615d 100644 --- a/src/J2N/Collections/Generic/SortedSet.TreeSubSet.cs +++ b/src/J2N/Collections/Generic/SortedSet.TreeSubSet.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; #if FEATURE_SERIALIZABLE @@ -76,6 +77,7 @@ public TreeSubSet(SortedSet Underlying, [AllowNull] T Min, bool lowerBoundInc #if FEATURE_SERIALIZABLE [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")] [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "CA2236 doesn't fire on all target frameworks")] + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private TreeSubSet(SerializationInfo info, StreamingContext context) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. @@ -446,8 +448,12 @@ internal override void IntersectWithEnumerable(IEnumerable other) #if FEATURE_SERIALIZABLE + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) => GetObjectData(info, context); + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) diff --git a/src/J2N/Collections/Generic/SortedSet.cs b/src/J2N/Collections/Generic/SortedSet.cs index 33bff259..3b6d372c 100644 --- a/src/J2N/Collections/Generic/SortedSet.cs +++ b/src/J2N/Collections/Generic/SortedSet.cs @@ -6,6 +6,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; #if FEATURE_SERIALIZABLE @@ -236,6 +237,8 @@ public SortedSet(IEnumerable collection, IComparer? comparer) /// /// This constructor is called during deserialization to reconstitute an object that is transmitted over a stream. /// + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected SortedSet(SerializationInfo info, StreamingContext context) { siInfo = info; @@ -2107,6 +2110,8 @@ internal virtual bool versionUpToDate() #endif #if FEATURE_SERIALIZABLE + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) => GetObjectData(info, context); /// @@ -2119,6 +2124,8 @@ internal virtual bool versionUpToDate() /// of the serialized stream associated with the object. /// is null. /// Calling this method is an O(n) operation, where n is . + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) @@ -2531,6 +2538,7 @@ internal Enumerator(SortedSet set, bool reverse) #if FEATURE_SERIALIZABLE + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private Enumerator(SerializationInfo info, StreamingContext context) { _tree = null!; @@ -2541,6 +2549,8 @@ private Enumerator(SerializationInfo info, StreamingContext context) _siInfo = info; } + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) diff --git a/src/J2N/IO/BufferOverflowException.cs b/src/J2N/IO/BufferOverflowException.cs index 668a033f..13d7ff41 100644 --- a/src/J2N/IO/BufferOverflowException.cs +++ b/src/J2N/IO/BufferOverflowException.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.ComponentModel; namespace J2N.IO @@ -63,6 +64,7 @@ public BufferOverflowException(string message, Exception innerException) : base( /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private BufferOverflowException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/src/J2N/IO/BufferUnderflowException.cs b/src/J2N/IO/BufferUnderflowException.cs index f1e091e6..4bae7fb1 100644 --- a/src/J2N/IO/BufferUnderflowException.cs +++ b/src/J2N/IO/BufferUnderflowException.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.ComponentModel; namespace J2N.IO @@ -61,6 +62,7 @@ public BufferUnderflowException(string message, Exception innerException) : base /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private BufferUnderflowException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/src/J2N/IO/InvalidMarkException.cs b/src/J2N/IO/InvalidMarkException.cs index c0002303..7a83f5e6 100644 --- a/src/J2N/IO/InvalidMarkException.cs +++ b/src/J2N/IO/InvalidMarkException.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.ComponentModel; namespace J2N.IO @@ -63,6 +64,7 @@ public InvalidMarkException(string message, Exception innerException) : base(mes /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private InvalidMarkException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/src/J2N/IO/ReadOnlyBufferException.cs b/src/J2N/IO/ReadOnlyBufferException.cs index 269eb923..269298c5 100644 --- a/src/J2N/IO/ReadOnlyBufferException.cs +++ b/src/J2N/IO/ReadOnlyBufferException.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.ComponentModel; namespace J2N.IO @@ -63,6 +64,7 @@ public ReadOnlyBufferException(string message, Exception innerException) : base( /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] private ReadOnlyBufferException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } diff --git a/src/J2N/Text/ParseException.cs b/src/J2N/Text/ParseException.cs index 2a8dc7cc..4ef7272b 100644 --- a/src/J2N/Text/ParseException.cs +++ b/src/J2N/Text/ParseException.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.ComponentModel; namespace J2N.Text { @@ -82,17 +83,22 @@ internal ParseException(string message, Exception innerException) : base(message /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] protected ParseException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { errorOffset = info.GetInt32(ErrorOffsetName); } +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member /// /// Sets the with information about the exception. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] + [EditorBrowsable(EditorBrowsableState.Never)] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { if (info is null) @@ -102,6 +108,7 @@ public override void GetObjectData(System.Runtime.Serialization.SerializationInf base.GetObjectData(info, context); } +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member #endif /// diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/Dictionary/Dictionary.Generic.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/Dictionary/Dictionary.Generic.Tests.cs index 86472989..a5a9d9c5 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/Dictionary/Dictionary.Generic.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/Dictionary/Dictionary.Generic.Tests.cs @@ -620,6 +620,7 @@ public void TrimExcess_Generic_DoesInvalidateEnumeration() #endregion #region Non-randomized comparers + [Fact] public void Dictionary_Comparer_NonRandomizedStringComparers() { @@ -641,14 +642,17 @@ void RunTest(SCG.IEqualityComparer comparer) Assert.Same(expected, dict.EqualityComparer); +#if FEATURE_SERIALIZABLE // Then pretend to serialize the dictionary and check the stored Comparer instance SerializationInfo si = new SerializationInfo(typeof(Dictionary), new FormatterConverter()); dict.GetObjectData(si, new StreamingContext(StreamingContextStates.All)); Assert.Same(expected, si.GetValue("EqualityComparer", typeof(SCG.IEqualityComparer))); +#endif } } + #endregion } } diff --git a/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj b/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj index d1bc3cd3..3a5950da 100644 --- a/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj +++ b/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj @@ -11,6 +11,8 @@ $(NoWarn);xUnit2015 $(NoWarn);xUnit2017 $(NoWarn);SYSLIB0011 + $(NoWarn);SYSLIB0050 + $(NoWarn);SYSLIB0051 diff --git a/tests/J2N.Tests/J2N.Tests.csproj b/tests/J2N.Tests/J2N.Tests.csproj index 8c5ee760..56076346 100644 --- a/tests/J2N.Tests/J2N.Tests.csproj +++ b/tests/J2N.Tests/J2N.Tests.csproj @@ -15,6 +15,8 @@ $(NoWarn);IDE0054 $(NoWarn);SYSLIB0011 + $(NoWarn);SYSLIB0050 + $(NoWarn);SYSLIB0051 From 0052860048ecb08e92bd89fa5208b90c1338f347 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 13:22:10 +0700 Subject: [PATCH 09/20] run-tests-on-os.yml: Fixed target frameworks variable name so the additional SDKs will install --- .build/azure-templates/run-tests-on-os.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.build/azure-templates/run-tests-on-os.yml b/.build/azure-templates/run-tests-on-os.yml index 09aa8273..b6ed42b1 100644 --- a/.build/azure-templates/run-tests-on-os.yml +++ b/.build/azure-templates/run-tests-on-os.yml @@ -63,7 +63,7 @@ steps: & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" displayName: 'Use .NET SDK ${{ parameters.dotNetSdkVersion }} (x86)' - condition: and(succeeded(), contains('${{ parameters.framework }}', 'net8.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) + condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net8.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: UseDotNet@2 displayName: 'Use .NET SDK 6.0.421' @@ -71,7 +71,7 @@ steps: packageType: 'sdk' version: '6.0.421' performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' - condition: and(succeeded(), contains('${{ parameters.framework }}', 'net6.')) + condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net6.')) # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. @@ -88,7 +88,7 @@ steps: & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" displayName: 'Use .NET SDK 6.0.421 (x86)' - condition: and(succeeded(), contains('${{ parameters.framework }}', 'net6.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) + condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net6.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: UseDotNet@2 displayName: 'Use .NET SDK 5.0.408' @@ -96,7 +96,7 @@ steps: packageType: 'sdk' version: '5.0.408' performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' - condition: and(succeeded(), contains('${{ parameters.framework }}', 'net5.')) + condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net5.')) # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. @@ -113,7 +113,7 @@ steps: & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" displayName: 'Use .NET SDK 5.0.408 (x86)' - condition: and(succeeded(), contains('${{ parameters.framework }}', 'net5.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) + condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net5.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: DownloadPipelineArtifact@2 displayName: 'Download Pipeline Artifacts: ${{ parameters.binaryArtifactName }}' From a3932db9f96ef966b2cc40803839e63d1f145f84 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 15:04:50 +0700 Subject: [PATCH 10/20] BUG: J2N.Collections.Tests.HashSet_IEnumerable_NonGeneric_Tests: Fixed tests to test our HashSet implementation instead of the one in the .NET runtime --- .../HashSet.Generic.Tests.AsNonGenericIEnumerable.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs b/tests/J2N.Tests.xUnit/Collections/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs index ce47e596..b88b25a5 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using JCG = J2N.Collections.Generic; namespace J2N.Collections.Tests { @@ -12,7 +13,7 @@ public class HashSet_IEnumerable_NonGeneric_Tests : IEnumerable_NonGeneric_Tests { protected override IEnumerable NonGenericIEnumerableFactory(int count) { - var set = new HashSet(); + var set = new JCG.HashSet(); int seed = 12354; while (set.Count < count) set.Add(CreateT(set, seed++)); @@ -35,7 +36,7 @@ protected override IEnumerable GetModifyEnumerables(ModifyOper { yield return (IEnumerable enumerable) => { - HashSet casted = ((HashSet)enumerable); + JCG.HashSet casted = ((JCG.HashSet)enumerable); if (casted.Count > 0) { casted.Clear(); @@ -46,7 +47,7 @@ protected override IEnumerable GetModifyEnumerables(ModifyOper } } - protected string CreateT(HashSet set, int seed) + protected string CreateT(JCG.HashSet set, int seed) { int stringLength = seed % 10 + 5; Random rand = new Random(seed); From fde7ae65cd4e8c0c260b8d7bc6aeeeac5e6010e4 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 15:06:10 +0700 Subject: [PATCH 11/20] J2N.Tests.xUnit: Updated List tests to pass with the current implementation, for now. --- .../Collections/Generic/List/List.Generic.Tests.cs | 3 +++ tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs | 2 ++ .../Collections/Generic/List/List.SubList.Grandchild.Tests.cs | 2 ++ .../Collections/Generic/List/List.SubList.Tests.cs | 2 ++ 4 files changed, 9 insertions(+) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.Tests.cs index fc7bc7b0..d052af2d 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.Tests.cs @@ -14,6 +14,9 @@ namespace J2N.Collections.Tests public abstract partial class List_Generic_Tests : IList_Generic_Tests { #region IList Helper Methods + //protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + //protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override SCG.IList GenericIListFactory() { diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs index c1bfc9cb..8b596e6b 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs @@ -10,6 +10,7 @@ namespace J2N.Collections.Tests { public class List_Generic_Tests_string : List_Generic_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override string CreateT(int seed) { int stringLength = seed % 10 + 5; @@ -22,6 +23,7 @@ protected override string CreateT(int seed) public class List_Generic_Tests_int : List_Generic_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override int CreateT(int seed) { Random rand = new Random(seed); diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs index 3d67c12b..4fa7f31c 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs @@ -7,6 +7,7 @@ namespace J2N.Collections.Tests { public class List_SubList_Grandchild_Tests_int : List_SubList_Grandchild_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override bool DefaultValueAllowed => true; protected override int CreateT(int seed) @@ -18,6 +19,7 @@ protected override int CreateT(int seed) public class List_SubList_Grandchild_Tests_string : List_SubList_Grandchild_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override string CreateT(int seed) { int stringLength = seed % 10 + 5; diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs index 804f8ed9..da127013 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs @@ -7,6 +7,7 @@ namespace J2N.Collections.Tests { public class List_SubList_Tests_int : List_SubList_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override bool DefaultValueAllowed => true; protected override int CreateT(int seed) @@ -18,6 +19,7 @@ protected override int CreateT(int seed) public class List_SubList_Tests_string : List_SubList_Tests { + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override string CreateT(int seed) { int stringLength = seed % 10 + 5; From b983d612cc46ff0292ef85d06585799e573d64fd Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Sun, 15 Sep 2024 15:08:22 +0700 Subject: [PATCH 12/20] BUG: J2N.Collections.Tests.SortedDictionary_IDictionary_NonGeneric_Tests: Fixed tests to use our SortedDictionary implementation instead of the one in the .NET runtime --- .../SortedDictionary.Tests.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs index 418f6191..a30c3a5c 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using Xunit; +using JCG = J2N.Collections.Generic; namespace J2N.Collections.Tests { @@ -15,7 +16,7 @@ public class SortedDictionary_IDictionary_NonGeneric_Tests : IDictionary_NonGene protected override IDictionary NonGenericIDictionaryFactory() { - return new SortedDictionary(); + return new JCG.SortedDictionary(); } /// @@ -47,7 +48,7 @@ protected override object CreateTValue(int seed) [Fact] public void IDictionary_NonGeneric_ItemSet_NullValueWhenDefaultValueIsNonNull() { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); Assert.Throws(() => dictionary[GetNewKey(dictionary)] = null); } @@ -56,7 +57,7 @@ public void IDictionary_NonGeneric_ItemSet_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); AssertExtensions.Throws("key", () => dictionary[23] = CreateTValue(12345)); Assert.Empty(dictionary); } @@ -67,7 +68,7 @@ public void IDictionary_NonGeneric_ItemSet_ValueOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); object missingKey = GetNewKey(dictionary); AssertExtensions.Throws("value", () => dictionary[missingKey] = 324); Assert.Empty(dictionary); @@ -79,7 +80,7 @@ public void IDictionary_NonGeneric_Add_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); object missingKey = 23; AssertExtensions.Throws("key", () => dictionary.Add(missingKey, CreateTValue(12345))); Assert.Empty(dictionary); @@ -91,7 +92,7 @@ public void IDictionary_NonGeneric_Add_ValueOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); object missingKey = GetNewKey(dictionary); AssertExtensions.Throws("value", () => dictionary.Add(missingKey, 324)); Assert.Empty(dictionary); @@ -103,7 +104,7 @@ public void IDictionary_NonGeneric_Add_NullValueWhenDefaultTValueIsNonNull() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); object missingKey = GetNewKey(dictionary); Assert.Throws(() => dictionary.Add(missingKey, null)); Assert.Empty(dictionary); @@ -115,7 +116,7 @@ public void IDictionary_NonGeneric_Contains_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new SortedDictionary(); + IDictionary dictionary = new JCG.SortedDictionary(); Assert.False(dictionary.Contains(1)); } } @@ -124,7 +125,7 @@ public void IDictionary_NonGeneric_Contains_KeyOfWrongType() public void CantAcceptDuplicateKeysFromSourceDictionary() { Dictionary source = new Dictionary { { "a", 1 }, { "A", 1 } }; - AssertExtensions.Throws(null, () => new SortedDictionary(source, StringComparer.OrdinalIgnoreCase)); + AssertExtensions.Throws(null, () => new JCG.SortedDictionary(source, StringComparer.OrdinalIgnoreCase)); } #endregion From 10603cdaa8c7cd3255a67e8875ba94b4aef15680 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 14:31:40 +0700 Subject: [PATCH 13/20] SWEEP: Upgraded SortedSet and SortedDictionary to the .NET 8.0 implementation (fixes #107, fixes #108). Upgraded collection test framework and tests to account for new or differing behaviors in .NET 8.0 (excluding IList features). Added IReadOnlySet to all set types. --- Directory.Build.targets | 15 +- .../Collections/Generic/EnumerableHelpers.cs | 4 + src/J2N/Collections/Generic/HashSet.cs | 3 + src/J2N/Collections/Generic/LinkedHashSet.cs | 3 + .../Collections/Generic/SortedDictionary.cs | 202 ++++++++---------- src/J2N/Collections/Generic/SortedSet.cs | 96 +++++---- .../Collections/ObjectModel/ReadOnlySet.cs | 24 +++ .../Extensions/SubList/SubList.Tests.cs | 17 ++ .../Collections/Generic/List/List.Generic.cs | 9 + .../List/List.SubList.Grandchild.Tests.cs | 8 + .../Generic/List/List.SubList.Tests.cs | 8 + .../SortedDictionary.Generic.Tests.Keys.cs | 19 +- .../SortedDictionary.Generic.Tests.Values.cs | 15 +- .../SortedDictionary.Generic.Tests.cs | 20 +- .../SortedDictionary.Generic.cs | 20 +- .../SortedDictionary.Tests.cs | 31 +-- .../Collections/ICollection.Generic.Tests.cs | 8 +- .../Collections/ISet.Generic.Tests.cs | 174 +++++++++------ 18 files changed, 420 insertions(+), 256 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index c5698d8e..e3462f48 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -25,7 +25,7 @@ - + $(DefineConstants);FEATURE_UNICODE_DEFINED_0x9FFF @@ -36,6 +36,7 @@ $(DefineConstants);FEATURE_ARRAY_CLEAR_ARRAY $(DefineConstants);FEATURE_RANDOM_NEXTINT64 $(DefineConstants);FEATURE_RANDOM_NEXTSINGLE + $(DefineConstants);FEATURE_READONLYSET $(DefineConstants);FEATURE_STRINGBUILDER_GETCHUNKS @@ -72,6 +73,7 @@ $(DefineConstants);FEATURE_NUMBER_TRYFORMAT $(DefineConstants);FEATURE_RANDOMNUMBERGENERATOR_FILL_SPAN $(DefineConstants);FEATURE_RUNTIMEHELPERS_ISREFERENCETYPEORCONTAINSREFERENCES + $(DefineConstants);FEATURE_STACK_TRYPOP $(DefineConstants);FEATURE_STRING_CONTAINS_CHAR $(DefineConstants);FEATURE_STRING_CREATE $(DefineConstants);FEATURE_STRINGBUILDER_APPEND_READONLYSPAN @@ -99,6 +101,17 @@ $(DefineConstants);FEATURE_CLONEABLE + + + + + $(DefineConstants);FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + + + diff --git a/src/J2N/Collections/Generic/EnumerableHelpers.cs b/src/J2N/Collections/Generic/EnumerableHelpers.cs index da253898..bc385e51 100644 --- a/src/J2N/Collections/Generic/EnumerableHelpers.cs +++ b/src/J2N/Collections/Generic/EnumerableHelpers.cs @@ -16,6 +16,10 @@ namespace J2N.Collections.Generic [SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "Using Microsoft's code styles")] internal static partial class EnumerableHelpers { + /// Gets an enumerator singleton for an empty collection. + internal static IEnumerator GetEmptyEnumerator() => + ((IEnumerable)Arrays.Empty()).GetEnumerator(); + /// Converts an enumerable to an array using the same logic as . /// The enumerable to convert. /// The number of items stored in the resulting array, 0-indexed. diff --git a/src/J2N/Collections/Generic/HashSet.cs b/src/J2N/Collections/Generic/HashSet.cs index f1c7bbd4..a47dba3e 100644 --- a/src/J2N/Collections/Generic/HashSet.cs +++ b/src/J2N/Collections/Generic/HashSet.cs @@ -55,6 +55,9 @@ namespace J2N.Collections.Generic public class HashSet : ISet, ICollection, #if FEATURE_IREADONLYCOLLECTIONS IReadOnlyCollection, +#endif +#if FEATURE_READONLYSET + IReadOnlySet, #endif IStructuralEquatable, IStructuralFormattable #if FEATURE_SERIALIZABLE diff --git a/src/J2N/Collections/Generic/LinkedHashSet.cs b/src/J2N/Collections/Generic/LinkedHashSet.cs index 00ff3a10..f57bb740 100644 --- a/src/J2N/Collections/Generic/LinkedHashSet.cs +++ b/src/J2N/Collections/Generic/LinkedHashSet.cs @@ -55,6 +55,9 @@ namespace J2N.Collections.Generic public class LinkedHashSet : ICollection, ISet, #if FEATURE_IREADONLYCOLLECTIONS IReadOnlyCollection, +#endif +#if FEATURE_READONLYSET + IReadOnlySet, #endif IStructuralEquatable, IStructuralFormattable #if FEATURE_SERIALIZABLE diff --git a/src/J2N/Collections/Generic/SortedDictionary.cs b/src/J2N/Collections/Generic/SortedDictionary.cs index fa95db2d..68ba2726 100644 --- a/src/J2N/Collections/Generic/SortedDictionary.cs +++ b/src/J2N/Collections/Generic/SortedDictionary.cs @@ -153,11 +153,22 @@ public SortedDictionary(IDictionary dictionary, IComparer? c throw new ArgumentNullException(nameof(dictionary)); } - _set = new TreeSet>(new KeyValuePairComparer(comparer)); + var keyValuePairComparer = new KeyValuePairComparer(comparer); - foreach (KeyValuePair pair in dictionary) + if (dictionary is SortedDictionary sortedDictionary && + sortedDictionary._set.Comparer is KeyValuePairComparer kv && + kv.keyComparer.Equals(keyValuePairComparer.keyComparer)) + { + _set = new TreeSet>(sortedDictionary._set, keyValuePairComparer); + } + else { - _set.Add(pair); + _set = new TreeSet>(keyValuePairComparer); + + foreach (KeyValuePair pair in dictionary) + { + _set.Add(pair); + } } } @@ -273,7 +284,7 @@ bool ICollection>.Remove(KeyValuePair k /// Getting the value of this property is an O(log n) operation; setting the property is also /// an O(log n) operation. /// - public TValue this[TKey key] + public TValue this[[AllowNull]TKey key] { get { @@ -285,7 +296,7 @@ public TValue this[TKey key] TreeSet>.Node? node = _set.FindNode(new KeyValuePair(key, default!)); if (node == null) { - throw new KeyNotFoundException(J2N.SR.Format(SR.Arg_KeyNotFoundWithKey, key)); + throw new KeyNotFoundException(J2N.SR.Format(SR.Arg_KeyNotFoundWithKey, key?.ToString() ?? "null")); } return node.Item.Value; @@ -349,14 +360,7 @@ public IComparer Comparer /// /// Getting the value of this property is an O(1) operation. /// - public ICollection Keys - { - get - { - if (_keys == null) _keys = new KeyCollection(this); - return _keys; - } - } + public KeyCollection Keys => _keys ??= new KeyCollection(this); ICollection IDictionary.Keys => Keys; @@ -380,14 +384,7 @@ public ICollection Keys /// /// Getting the value of this property is an O(1) operation. /// - public ICollection Values - { - get - { - if (_values == null) _values = new ValueCollection(this); - return _values; - } - } + public ValueCollection Values => _values ??= new ValueCollection(this); ICollection IDictionary.Values => Values; @@ -415,13 +412,13 @@ public ICollection Values /// /// This method is an O(log n) operation, where n is . /// - public void Add(TKey key, TValue value) + public void Add([AllowNull] TKey key, [AllowNull]TValue value) { //if (key == null) // J2N: Making key nullable //{ // throw new ArgumentNullException(nameof(key)); //} - _set.Add(new KeyValuePair(key, value)); + _set.Add(new KeyValuePair(key!, value!)); } /// @@ -447,14 +444,14 @@ public void Clear() /// true if the contains an element /// with the specified key; otherwise, false. /// This method is an O(log n) operation. - public bool ContainsKey(TKey key) + public bool ContainsKey([AllowNull] TKey key) { //if (key == null) // J2N: Making key nullable //{ // throw new ArgumentNullException(nameof(key)); //} - return _set.Contains(new KeyValuePair(key, default!)); + return _set.Contains(new KeyValuePair(key!, default!)); } /// @@ -475,7 +472,7 @@ public bool ContainsValue(TValue value) { // NOTE: We do this check here to override the .NET default equality comparer // with J2N's version - return ContainsValue(value, EqualityComparer.Default); + return ContainsValue(value, null); } /// @@ -493,14 +490,14 @@ public bool ContainsValue(TValue value) /// is proportional to . That is, this method is an O(n) operation, /// where n is . /// - public bool ContainsValue(TValue value, IEqualityComparer valueComparer) // Overload added so end user can override J2N's equality comparer + public bool ContainsValue([AllowNull] TValue value, IEqualityComparer? valueComparer) // Overload added so end user can override J2N's equality comparer { bool found = false; - if (value == null) + if (value is null) { _set.InOrderTreeWalk(delegate (TreeSet>.Node node) { - if (node.Item.Value == null) + if (node.Item.Value is null) { found = true; return false; // stop the walk @@ -510,6 +507,7 @@ public bool ContainsValue(TValue value, IEqualityComparer valueComparer) } else { + valueComparer ??= EqualityComparer.Default; _set.InOrderTreeWalk(delegate (TreeSet>.Node node) { if (valueComparer.Equals(node.Item.Value, value)) @@ -588,15 +586,11 @@ public void CopyTo(KeyValuePair[] array, int index) /// /// This method is an O(1) operation. /// - public Enumerator GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + public Enumerator GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); - IEnumerator> IEnumerable>.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator> IEnumerable>.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator>() : + GetEnumerator(); /// /// Removes the element with the specified key from the . @@ -610,14 +604,14 @@ IEnumerator> IEnumerable>. /// /// This method is an O(log n) operation. /// - public bool Remove(TKey key) + public bool Remove([AllowNull] TKey key) { //if (key == null) // J2N: Making key nullable //{ // throw new ArgumentNullException(nameof(key)); //} - return _set.Remove(new KeyValuePair(key, default!)); + return _set.Remove(new KeyValuePair(key!, default!)); } /// @@ -634,11 +628,11 @@ public bool Remove(TKey key) /// // J2N: This is an extension method on IDictionary, but only for .NET Standard 2.1+. // It is redefined here to ensure we have it in prior platforms. - public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) + public bool Remove([AllowNull] TKey key, [MaybeNullWhen(false)] out TValue value) { if (TryGetValue(key, out value)) { - _set.Remove(new KeyValuePair(key, default!)); + _set.Remove(new KeyValuePair(key!, default!)); return true; } @@ -658,7 +652,7 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) /// does nothing and returns false. // J2N: This is an extension method on IDictionary, but only for .NET Standard 2.1+. // It is redefined here to ensure we have it in prior platforms. - public bool TryAdd(TKey key, TValue value) + public bool TryAdd([AllowNull] TKey key, TValue value) { if (!ContainsKey(key)) { @@ -695,7 +689,7 @@ public bool TryAdd(TKey key, TValue value) #pragma warning disable CS8767 // Nullability of reference types in type of parameter 'value' of 'bool Dictionary.TryGetValue(TKey key, out TValue value)' doesn't match implicitly implemented member 'bool IDictionary.TryGetValue(TKey key, out TValue value)' (possibly because of nullability attributes). - public bool TryGetValue([AllowNull, MaybeNull] TKey key, [MaybeNullWhen(false)] out TValue value) + public bool TryGetValue([AllowNull] TKey key, [MaybeNullWhen(false)] out TValue value) #pragma warning restore CS8767 // Nullability of reference types in type of parameter 'value' of 'bool Dictionary.TryGetValue(TKey key, out TValue value)' doesn't match implicitly implemented member 'bool IDictionary.TryGetValue(TKey key, out TValue value)' (possibly because of nullability attributes). { //if (key == null) // J2N: Making key nullable @@ -730,21 +724,21 @@ bool IDictionary.IsReadOnly ICollection IDictionary.Keys { - get { return (ICollection)Keys; } + get { return Keys; } } ICollection IDictionary.Values { - get { return (ICollection)Values; } + get { return Values; } } - object? IDictionary.this[object key] + object? IDictionary.this[object? key] { get { if (IsCompatibleKey(key)) { - if (TryGetValue((TKey)key, out TValue? value)) + if (TryGetValue((TKey)key!, out TValue? value)) { return value; } @@ -770,7 +764,7 @@ ICollection IDictionary.Values TKey tempKey = (TKey)key!; try { - this[tempKey!] = (TValue)value!; + this[tempKey] = (TValue)value!; } catch (InvalidCastException) { @@ -803,7 +797,7 @@ void IDictionary.Add(object? key, object? value) try { - Add(tempKey!, (TValue)value!); + Add(tempKey, (TValue)value!); } catch (InvalidCastException) { @@ -837,10 +831,7 @@ private static bool IsCompatibleKey(object? key) return (key is TKey); } - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return new Enumerator(this, Enumerator.DictEntry); - } + IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DictEntry); void IDictionary.Remove(object? key) { @@ -854,10 +845,7 @@ void IDictionary.Remove(object? key) object ICollection.SyncRoot => ((ICollection)_set).SyncRoot; - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); #endregion @@ -1042,8 +1030,8 @@ public virtual string ToString(string format) [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Collection design requires this to be public")] public struct Enumerator : IEnumerator>, IDictionaryEnumerator { - private readonly IEnumerator> _treeEnum; - private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? + private /*readonly*/ TreeSet>.Enumerator _treeEnum; + private /*readonly*/ int _getEnumeratorRetType; // What should Enumerator.Current return? internal const int KeyValuePair = 1; internal const int DictEntry = 2; @@ -1123,7 +1111,7 @@ internal bool NotStartedOrEnded { get { - return ((SortedSet>.Enumerator)_treeEnum).NotStartedOrEnded; + return _treeEnum.NotStartedOrEnded; } } @@ -1138,7 +1126,7 @@ void IEnumerator.Reset() _treeEnum.Reset(); } - object IEnumerator.Current + object? IEnumerator.Current { get { @@ -1225,7 +1213,7 @@ DictionaryEntry IDictionaryEnumerator.Entry [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Collection design requires this to be public")] - internal sealed class KeyCollection : ICollection, ICollection + public sealed class KeyCollection : ICollection, ICollection #if FEATURE_IREADONLYCOLLECTIONS , IReadOnlyCollection #endif @@ -1290,20 +1278,13 @@ public KeyCollection(SortedDictionary dictionary) /// /// Default implementations of collections in the namespace are not synchronized. /// - public IEnumerator GetEnumerator() - { - return new Enumerator(_dictionary); - } + public Enumerator GetEnumerator() => new Enumerator(_dictionary); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); /// /// Copies the elements to an existing one-dimensional array, @@ -1349,17 +1330,12 @@ void ICollection.CopyTo(Array array, int index) if (array.Length - index < _dictionary.Count) throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); - TKey[]? keys = array as TKey[]; - if (keys != null) + if (array is TKey[] keys) { CopyTo(keys, index); } else { - if (!(array is object?[])) - { - throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); - } try { object?[] objects = (object?[])array; @@ -1396,7 +1372,12 @@ void ICollection.Clear() throw new NotSupportedException(SR.NotSupported_KeyCollectionSet); } - bool ICollection.Contains(TKey item) + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . + /// true if item is found in the ; otherwise, false. + public bool Contains([AllowNull] TKey item) { return _dictionary.ContainsKey(item); } @@ -1449,7 +1430,7 @@ bool ICollection.Remove(TKey item) /// [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Collection design requires this to be public")] - internal struct Enumerator : IEnumerator, IEnumerator + public struct Enumerator : IEnumerator, IEnumerator { [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Following Microsoft's code style")] private SortedDictionary.Enumerator _dictEnum; @@ -1562,7 +1543,7 @@ void IEnumerator.Reset() [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Collection design requires this to be public")] - internal sealed class ValueCollection : ICollection, ICollection + public sealed class ValueCollection : ICollection, ICollection #if FEATURE_IREADONLYCOLLECTIONS , IReadOnlyCollection #endif @@ -1627,20 +1608,13 @@ public ValueCollection(SortedDictionary dictionary) /// /// This method is an O(1) operation. /// - public IEnumerator GetEnumerator() - { - return new Enumerator(_dictionary); - } + public Enumerator GetEnumerator() => new Enumerator(_dictionary); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); /// /// Copies the elements to an existing one-dimensional @@ -1699,17 +1673,12 @@ void ICollection.CopyTo(Array array, int index) throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); } - TValue[]? values = array as TValue[]; - if (values != null) + if (array is TValue[] values) { CopyTo(values, index); } else { - if (!(array is object?[])) - { - throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array)); - } try { object?[] objects = (object?[])array; @@ -1803,9 +1772,9 @@ object ICollection.SyncRoot /// [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Collection design requires this to be public")] - internal struct Enumerator : IEnumerator, IEnumerator + public struct Enumerator : IEnumerator, IEnumerator { - private readonly SortedDictionary.Enumerator _dictEnum; + private SortedDictionary.Enumerator _dictEnum; internal Enumerator(SortedDictionary dictionary) { @@ -1900,7 +1869,7 @@ void IEnumerator.Reset() #if FEATURE_SERIALIZABLE [Serializable] #endif - internal sealed class KeyValuePairComparer : SCG.Comparer> + internal sealed class KeyValuePairComparer : SCG.Comparer> // J2N TODO: API - This is public in .NET, but I cannot find any docs for it. { internal IComparer keyComparer; // Do not rename (binary serialization) @@ -1920,6 +1889,21 @@ public override int Compare(KeyValuePair x, KeyValuePair x, KeyValuePair : SortedSet + internal sealed class TreeSet : SortedSet // J2N TODO: API - This is public in .NET, but I cannot find any docs for it. { public TreeSet() : base() - { } + { /* Intentionally blank */ } + + public TreeSet(IComparer comparer) : base(comparer) { /* Intentionally blank */ } - public TreeSet(IComparer comparer) : base(comparer) { } + internal TreeSet(TreeSet set, IComparer? comparer) : base(set, comparer) { /* Intentionally blank */ } #if FEATURE_SERIALIZABLE [Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] - private TreeSet(SerializationInfo siInfo, StreamingContext context) : base(siInfo, context) { } + private TreeSet(SerializationInfo siInfo, StreamingContext context) : base(siInfo, context) { /* Intentionally blank */ } #endif internal override bool AddIfNotPresent(T item) diff --git a/src/J2N/Collections/Generic/SortedSet.cs b/src/J2N/Collections/Generic/SortedSet.cs index 3b6d372c..642b81dd 100644 --- a/src/J2N/Collections/Generic/SortedSet.cs +++ b/src/J2N/Collections/Generic/SortedSet.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using J2N.Collections.ObjectModel; +using J2N.Numerics; using J2N.Text; using System; using System.Collections; @@ -91,6 +92,9 @@ internal enum TreeRotation : byte public partial class SortedSet : ISet, ICollection, ICollection, #if FEATURE_IREADONLYCOLLECTIONS IReadOnlyCollection, +#endif +#if FEATURE_READONLYSET + IReadOnlySet, #endif IStructuralEquatable, IStructuralFormattable #if FEATURE_SERIALIZABLE @@ -167,7 +171,7 @@ public SortedSet(IComparer? comparer) /// This constructor is an O(n log n) operation, where n is the number of elements /// in the parameter. /// - public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { } + public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { /* Intentionally empty */ } /// @@ -731,7 +735,6 @@ internal virtual bool DoRemove(T item) { parentOfMatch = newGrandParent; } - grandParent = newGrandParent; } } } @@ -911,7 +914,7 @@ void ICollection.CopyTo(Array array, int index) /// /// This method is an O(log n) operation. /// - public IEnumerator GetEnumerator() => new Enumerator(this); + public Enumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -1061,9 +1064,9 @@ internal virtual int InternalIndexOf(T item) return -1; } - internal Node? FindRange([AllowNull] T from, [AllowNull] T to) => FindRange(from, to, lowerBoundInclusive: true, upperBoundInclusive: true, lowerBoundActive: true, upperBoundActive: true); + internal Node? FindRange(T? from, T? to) => FindRange(from, to, lowerBoundInclusive: true, upperBoundInclusive: true, lowerBoundActive: true, upperBoundActive: true); - internal Node? FindRange([AllowNull] T from, [AllowNull] T to, bool lowerBoundInclusive, bool upperBoundInclusive, bool lowerBoundActive, bool upperBoundActive) + internal Node? FindRange(T? from, T? to, bool lowerBoundInclusive, bool upperBoundInclusive, bool lowerBoundActive, bool upperBoundActive) { Node? current = root; while (current != null) @@ -1200,8 +1203,8 @@ public void UnionWith(IEnumerable other) // First do a merge sort to an array. T[] merged = new T[asSorted.Count + this.Count]; int c = 0; - IEnumerator mine = this.GetEnumerator(); - IEnumerator theirs = asSorted.GetEnumerator(); + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = asSorted.GetEnumerator(); bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); while (!mineEnded && !theirsEnded) { @@ -1356,8 +1359,8 @@ public virtual void IntersectWith(IEnumerable other) // First do a merge sort to an array. T[] merged = new T[this.Count]; int c = 0; - IEnumerator mine = this.GetEnumerator(); - IEnumerator theirs = asSorted.GetEnumerator(); + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = asSorted.GetEnumerator(); bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); T? max = Max; @@ -1779,8 +1782,8 @@ public bool SetEquals(IEnumerable other) SortedSet? asSorted = other as SortedSet; if (asSorted != null && HasEqualComparer(asSorted)) { - IEnumerator mine = GetEnumerator(); - IEnumerator theirs = asSorted.GetEnumerator(); + Enumerator mine = GetEnumerator(); + Enumerator theirs = asSorted.GetEnumerator(); bool mineEnded = !mine.MoveNext(); bool theirsEnded = !theirs.MoveNext(); while (!mineEnded && !theirsEnded) @@ -1967,11 +1970,9 @@ public int RemoveWhere(Predicate match) /// If the has no elements, then the property returns /// the default value of . /// - [MaybeNull] - public T Min => MinInternal; + public T? Min => MinInternal; - [MaybeNull] - internal virtual T MinInternal + internal virtual T? MinInternal { get { @@ -1997,11 +1998,9 @@ internal virtual T MinInternal /// If the has no elements, then the property returns /// the default value of . /// - [MaybeNull] - public T Max => MaxInternal; + public T? Max => MaxInternal; - [MaybeNull] - internal virtual T MaxInternal + internal virtual T? MaxInternal { get { @@ -2054,7 +2053,7 @@ public IEnumerable Reverse() /// , but provides a window into the underlying itself. /// You can make changes in both the view and in the underlying . /// - public virtual SortedSet GetViewBetween([AllowNull] T lowerValue, [AllowNull] T upperValue) + public virtual SortedSet GetViewBetween(T? lowerValue, T? upperValue) { if (Comparer.Compare(lowerValue!, upperValue!) > 0) { @@ -2088,7 +2087,7 @@ public virtual SortedSet GetViewBetween([AllowNull] T lowerValue, [AllowNull] /// , but provides a window into the underlying itself. /// You can make changes in both the view and in the underlying . /// - public virtual SortedSet GetViewBetween([AllowNull] T lowerValue, bool lowerValueInclusive, [AllowNull] T upperValue, bool upperValueInclusive) + public virtual SortedSet GetViewBetween(T? lowerValue, bool lowerValueInclusive, T? upperValue, bool upperValueInclusive) { if (Comparer.Compare(lowerValue!, upperValue!) > 0) { @@ -2240,6 +2239,32 @@ public Node DeepClone(int count) Debug.Assert(count == GetCount()); #endif +#if FEATURE_STACK_TRYPOP + Node newRoot = ShallowClone(); + + var pendingNodes = new Stack<(Node source, Node target)>(2 * Log2(count) + 2); + pendingNodes.Push((this, newRoot)); + + while (pendingNodes.TryPop(out var next)) + { + Node clonedNode; + + if (next.source.Left is Node left) + { + clonedNode = left.ShallowClone(); + next.target.Left = clonedNode; + pendingNodes.Push((left, clonedNode)); + } + + if (next.source.Right is Node right) + { + clonedNode = right.ShallowClone(); + next.target.Right = clonedNode; + pendingNodes.Push((right, clonedNode)); + } + } +#else + // J2N: This was code from .NET 5 // Breadth-first traversal to recreate nodes, preorder traversal to replicate nodes. var originalNodes = new Stack(2 * Log2(count) + 2); @@ -2276,7 +2301,7 @@ public Node DeepClone(int count) newRight = newRight.Left; } } - +#endif return newRoot; } @@ -2494,19 +2519,19 @@ private bool HasChildren(Node child1, Node child2) #if FEATURE_SERIALIZABLE [Serializable] #endif - internal struct Enumerator : IEnumerator, IEnumerator + public struct Enumerator : IEnumerator, IEnumerator #if FEATURE_SERIALIZABLE , ISerializable, IDeserializationCallback #endif { - private SortedSet _tree; - private int _version; + private /* readonly */ SortedSet _tree; + private /* readonly */ int _version; - private Stack _stack; + private /* readonly */ Stack _stack; private Node? _current; static readonly Node dummyNode = new Node(default!, NodeColor.Red); - private bool _reverse; + private /* readonly */ bool _reverse; #if FEATURE_SERIALIZABLE #pragma warning disable IDE0044 // Add readonly modifier @@ -2596,7 +2621,7 @@ private void Initialize() { _current = null; Node? node = _tree.root; - Node? next = null, other = null; + Node? next, other; while (node != null) { next = (_reverse ? node.Right : node.Left); @@ -2654,7 +2679,7 @@ public bool MoveNext() _current = _stack.Pop(); Node? node = (_reverse ? _current.Left : _current.Right); - Node? next = null, other = null; + Node? next, other; while (node != null) { next = (_reverse ? node.Right : node.Left); @@ -2679,7 +2704,7 @@ public bool MoveNext() /// /// Releases all resources used by the . /// - public void Dispose() { } + public void Dispose() { /* Intentionally blank */ } /// /// Gets the element at the current position of the enumerator. @@ -2785,16 +2810,7 @@ public bool TryGetValue(T equalValue, [MaybeNullWhen(false)] out T actualValue) } // Used for set checking operations (using enumerables) that rely on counting - private static int Log2(int value) - { - int result = 0; - while (value > 0) - { - result++; - value >>= 1; - } - return result; - } + private static int Log2(int value) => BitOperation.Log2((uint)value); #endregion diff --git a/src/J2N/Collections/ObjectModel/ReadOnlySet.cs b/src/J2N/Collections/ObjectModel/ReadOnlySet.cs index be2b1ca2..0351eb66 100644 --- a/src/J2N/Collections/ObjectModel/ReadOnlySet.cs +++ b/src/J2N/Collections/ObjectModel/ReadOnlySet.cs @@ -50,6 +50,9 @@ namespace J2N.Collections.ObjectModel public class ReadOnlySet : ISet, ICollection, #if FEATURE_IREADONLYCOLLECTIONS IReadOnlyCollection, +#endif +#if FEATURE_READONLYSET + IReadOnlySet, #endif IStructuralEquatable, IStructuralFormattable { @@ -344,6 +347,27 @@ bool ISet.Add(T item) #endregion + #region IReadOnlySet Members + +#if FEATURE_READONLYSET + + bool IReadOnlySet.Contains(T item) => set.Contains(item); + + bool IReadOnlySet.IsProperSubsetOf(IEnumerable other) => set.IsProperSubsetOf(other); + + bool IReadOnlySet.IsProperSupersetOf(IEnumerable other) => set.IsProperSupersetOf(other); + + bool IReadOnlySet.IsSubsetOf(IEnumerable other) => set.IsSubsetOf(other); + + bool IReadOnlySet.IsSupersetOf(IEnumerable other) => set.IsSupersetOf(other); + + bool IReadOnlySet.Overlaps(IEnumerable other) => set.Overlaps(other); + + bool IReadOnlySet.SetEquals(IEnumerable other) => set.SetEquals(other); +#endif + + #endregion + #region ICollection Members bool ICollection.IsSynchronized diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs index f31b6f77..7503eab4 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs @@ -43,6 +43,11 @@ protected override string CreateT(int seed) return Convert.ToBase64String(bytes); } + // J2N: See the comment in the root Directory.Build.targets file +#if !FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; +#endif + protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) @@ -66,6 +71,11 @@ protected override int CreateT(int seed) return rand.Next(); } + // J2N: See the comment in the root Directory.Build.targets file +#if !FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; +#endif + protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) @@ -83,6 +93,13 @@ protected override SCG.IList GenericIListFactory() public abstract class SubList_Tests : IList_Generic_Tests { +// // J2N: See the comment in the root Directory.Build.targets file +//#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW +// protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +//#else +// //protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; +//#endif + private SCG.List OriginalList { get; set; } #region IList Helper Methods diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs index 8b596e6b..d00eb755 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.Generic.cs @@ -42,6 +42,11 @@ protected override string CreateT(int seed) return Convert.ToBase64String(bytes); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif + protected override bool IsReadOnly => true; protected override IList GenericIListFactory(int setLength) @@ -65,6 +70,10 @@ protected override int CreateT(int seed) return rand.Next(); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif protected override bool IsReadOnly => true; protected override IList GenericIListFactory(int setLength) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs index 4fa7f31c..c884568f 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Grandchild.Tests.cs @@ -41,6 +41,10 @@ protected override string CreateT(int seed) return Convert.ToBase64String(bytes); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) @@ -64,6 +68,10 @@ protected override int CreateT(int seed) return rand.Next(); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs index da127013..c28a2c6e 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/List/List.SubList.Tests.cs @@ -41,6 +41,10 @@ protected override string CreateT(int seed) return Convert.ToBase64String(bytes); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) @@ -64,6 +68,10 @@ protected override int CreateT(int seed) return rand.Next(); } + // J2N: See the comment in the root Directory.Build.targets file +#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; +#endif protected override bool IsReadOnly => true; protected override SCG.IList GenericIListFactory(int setLength) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs index 0522d8b6..ff717e64 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs @@ -1,27 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +using J2N.Collections.Generic; using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using Xunit; +using SCG = System.Collections.Generic; namespace J2N.Collections.Tests { public class SortedDictionary_Generic_Tests_Keys : ICollection_Generic_Tests { - protected override bool DefaultValueAllowed => false; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool DefaultValueAllowed => true; // J2N supports null keys protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; - protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); - protected override ICollection GenericICollectionFactory() + protected override SCG.IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override SCG.ICollection GenericICollectionFactory() { return new SortedDictionary().Keys; } - protected override ICollection GenericICollectionFactory(int count) + protected override SCG.ICollection GenericICollectionFactory(int count) { SortedDictionary list = new SortedDictionary(); int seed = 13453; @@ -59,11 +61,12 @@ public void SortedDictionary_Generic_KeyCollection_GetEnumerator(int count) public class SortedDictionary_Generic_Tests_Keys_AsICollection : ICollection_NonGeneric_Tests { - protected override bool NullAllowed => true; + protected override bool NullAllowed => true; // J2N allows null keys protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; - protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override SCG.IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override ICollection NonGenericICollectionFactory() { return (ICollection)(new SortedDictionary().Keys); diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs index 671dbea5..51a3b59d 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs @@ -1,29 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +using J2N.Collections.Generic; using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using Xunit; +using SCG = System.Collections.Generic; namespace J2N.Collections.Tests { public class SortedDictionary_Generic_Tests_Values : ICollection_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override bool DefaultValueAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; - protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override SCG.IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); - protected override ICollection GenericICollectionFactory() + protected override SCG.ICollection GenericICollectionFactory() { return new SortedDictionary().Values; } - protected override ICollection GenericICollectionFactory(int count) + protected override SCG.ICollection GenericICollectionFactory(int count) { SortedDictionary list = new SortedDictionary(); int seed = 13453; @@ -64,8 +66,9 @@ public class SortedDictionary_Generic_Tests_Values_AsICollection : ICollection_N protected override bool NullAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; - protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override SCG.IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override bool SupportsSerialization => false; protected override ICollection NonGenericICollectionFactory() diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs index 96ac6515..9f10cdb5 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using J2N.Collections.Generic; using System; @@ -17,7 +16,9 @@ namespace J2N.Collections.Tests public abstract class SortedDictionary_Generic_Tests : IDictionary_Generic_Tests { #region IDictionary Helper Methods - + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool DefaultValueWhenNotAllowed_Throws { get { return false; } } protected override SCG.IDictionary GenericIDictionaryFactory() @@ -61,9 +62,18 @@ public void SortedDictionary_Generic_Constructor_IDictionary_IComparer(int count { SCG.IComparer comparer = GetKeyIComparer(); SCG.IDictionary source = GenericIDictionaryFactory(count); - SortedDictionary copied = new SortedDictionary(source, comparer); - Assert.Equal(source, copied); + SortedDictionary sourceSorted = new SortedDictionary(source, comparer); + Assert.Equal(source, sourceSorted); + Assert.Equal(comparer, sourceSorted.Comparer); + // Test copying a sorted dictionary. + SortedDictionary copied = new SortedDictionary(sourceSorted, comparer); + Assert.Equal(sourceSorted, copied); Assert.Equal(comparer, copied.Comparer); + // Test copying a sorted dictionary with a different comparer. + SCG.IComparer reverseComparer = SCG.Comparer.Create((key1, key2) => -comparer.Compare(key1, key2)); + SortedDictionary copiedReverse = new SortedDictionary(sourceSorted, reverseComparer); + Assert.Equal(sourceSorted, copiedReverse); + Assert.Equal(reverseComparer, copiedReverse.Comparer); } #endregion @@ -125,7 +135,7 @@ public void SortedDictionary_Generic_ContainsValue_DefaultValuePresent(int count public void SortedDictionary_Generic_DictionaryIsProperlySortedAccordingToComparer(int setLength) { SortedDictionary set = (SortedDictionary)GenericIDictionaryFactory(setLength); - List> expected = set.ToList(); + J2N.Collections.Generic.List> expected = set.ToList(); expected.Sort(GetIComparer()); int expectedIndex = 0; foreach (SCG.KeyValuePair value in set) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.cs index 761a5707..8f45950a 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Generic.cs @@ -1,18 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +using J2N.Collections.Generic; using System; -using System.Collections.Generic; using Xunit; +using SCG = System.Collections.Generic; namespace J2N.Collections.Tests { public class SortedDictionary_Generic_Tests_string_string : SortedDictionary_Generic_Tests { - protected override KeyValuePair CreateT(int seed) + protected override SCG.KeyValuePair CreateT(int seed) { - return new KeyValuePair(CreateTKey(seed), CreateTKey(seed + 500)); + return new SCG.KeyValuePair(CreateTKey(seed), CreateTKey(seed + 500)); } protected override string CreateTKey(int seed) @@ -33,10 +33,10 @@ protected override string CreateTValue(int seed) public class SortedDictionary_Generic_Tests_int_int : SortedDictionary_Generic_Tests { protected override bool DefaultValueAllowed { get { return true; } } - protected override KeyValuePair CreateT(int seed) + protected override SCG.KeyValuePair CreateT(int seed) { Random rand = new Random(seed); - return new KeyValuePair(rand.Next(), rand.Next()); + return new SCG.KeyValuePair(rand.Next(), rand.Next()); } protected override int CreateTKey(int seed) @@ -54,10 +54,10 @@ protected override int CreateTValue(int seed) //[OuterLoop] public class SortedDictionary_Generic_Tests_EquatableBackwardsOrder_int : SortedDictionary_Generic_Tests { - protected override KeyValuePair CreateT(int seed) + protected override SCG.KeyValuePair CreateT(int seed) { Random rand = new Random(seed); - return new KeyValuePair(new EquatableBackwardsOrder(rand.Next()), rand.Next()); + return new SCG.KeyValuePair(new EquatableBackwardsOrder(rand.Next()), rand.Next()); } protected override EquatableBackwardsOrder CreateTKey(int seed) @@ -72,9 +72,9 @@ protected override int CreateTValue(int seed) return rand.Next(); } - protected override IDictionary GenericIDictionaryFactory() + protected override SCG.IDictionary GenericIDictionaryFactory() { - return new J2N.Collections.Generic.SortedDictionary(); + return new SortedDictionary(); } } } diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs index a30c3a5c..63a31989 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/SortedDictionary/SortedDictionary.Tests.cs @@ -1,22 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +using J2N.Collections.Generic; using System; using System.Collections; -using System.Collections.Generic; using Xunit; -using JCG = J2N.Collections.Generic; +using SCG = System.Collections.Generic; namespace J2N.Collections.Tests { public class SortedDictionary_IDictionary_NonGeneric_Tests : IDictionary_NonGeneric_Tests { #region IDictionary Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + + protected override bool NullAllowed => true; // J2N allows null keys protected override IDictionary NonGenericIDictionaryFactory() { - return new JCG.SortedDictionary(); + return new SortedDictionary(); } /// @@ -48,7 +51,7 @@ protected override object CreateTValue(int seed) [Fact] public void IDictionary_NonGeneric_ItemSet_NullValueWhenDefaultValueIsNonNull() { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); Assert.Throws(() => dictionary[GetNewKey(dictionary)] = null); } @@ -57,7 +60,7 @@ public void IDictionary_NonGeneric_ItemSet_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); AssertExtensions.Throws("key", () => dictionary[23] = CreateTValue(12345)); Assert.Empty(dictionary); } @@ -68,7 +71,7 @@ public void IDictionary_NonGeneric_ItemSet_ValueOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); object missingKey = GetNewKey(dictionary); AssertExtensions.Throws("value", () => dictionary[missingKey] = 324); Assert.Empty(dictionary); @@ -80,7 +83,7 @@ public void IDictionary_NonGeneric_Add_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); object missingKey = 23; AssertExtensions.Throws("key", () => dictionary.Add(missingKey, CreateTValue(12345))); Assert.Empty(dictionary); @@ -92,7 +95,7 @@ public void IDictionary_NonGeneric_Add_ValueOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); object missingKey = GetNewKey(dictionary); AssertExtensions.Throws("value", () => dictionary.Add(missingKey, 324)); Assert.Empty(dictionary); @@ -104,7 +107,7 @@ public void IDictionary_NonGeneric_Add_NullValueWhenDefaultTValueIsNonNull() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); object missingKey = GetNewKey(dictionary); Assert.Throws(() => dictionary.Add(missingKey, null)); Assert.Empty(dictionary); @@ -116,7 +119,7 @@ public void IDictionary_NonGeneric_Contains_KeyOfWrongType() { if (!IsReadOnly) { - IDictionary dictionary = new JCG.SortedDictionary(); + IDictionary dictionary = new SortedDictionary(); Assert.False(dictionary.Contains(1)); } } @@ -125,7 +128,7 @@ public void IDictionary_NonGeneric_Contains_KeyOfWrongType() public void CantAcceptDuplicateKeysFromSourceDictionary() { Dictionary source = new Dictionary { { "a", 1 }, { "A", 1 } }; - AssertExtensions.Throws(null, () => new JCG.SortedDictionary(source, StringComparer.OrdinalIgnoreCase)); + AssertExtensions.Throws(null, () => new SortedDictionary(source, StringComparer.OrdinalIgnoreCase)); } #endregion @@ -137,7 +140,7 @@ public void CantAcceptDuplicateKeysFromSourceDictionary() public void ICollection_NonGeneric_CopyTo_ArrayOfIncorrectKeyValuePairType(int count) { ICollection collection = NonGenericICollectionFactory(count); - KeyValuePair[] array = new KeyValuePair[count * 3 / 2]; + SCG.KeyValuePair[] array = new SCG.KeyValuePair[count * 3 / 2]; AssertExtensions.Throws("array", null, () => collection.CopyTo(array, 0)); } @@ -146,7 +149,7 @@ public void ICollection_NonGeneric_CopyTo_ArrayOfIncorrectKeyValuePairType(int c public void ICollection_NonGeneric_CopyTo_ArrayOfCorrectKeyValuePairType(int count) { ICollection collection = NonGenericICollectionFactory(count); - KeyValuePair[] array = new KeyValuePair[count]; + SCG.KeyValuePair[] array = new SCG.KeyValuePair[count]; collection.CopyTo(array, 0); int i = 0; foreach (object obj in collection) diff --git a/tests/J2N.Tests.xUnit/Collections/ICollection.Generic.Tests.cs b/tests/J2N.Tests.xUnit/Collections/ICollection.Generic.Tests.cs index 1e4ca6a9..9c47110c 100644 --- a/tests/J2N.Tests.xUnit/Collections/ICollection.Generic.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/ICollection.Generic.Tests.cs @@ -408,8 +408,10 @@ public void ICollection_Generic_Contains_ValidValueOnCollectionContainingThatVal public void ICollection_Generic_Contains_DefaultValueOnCollectionNotContainingDefaultValue(int count) { ICollection collection = GenericICollectionFactory(count); - if (DefaultValueAllowed) + if (DefaultValueAllowed && default(T) is null) // it's true only for reference types and for Nullable + { Assert.False(collection.Contains(default(T))); + } } [Theory] @@ -491,7 +493,7 @@ public void ICollection_Generic_CopyTo_IndexEqualToArrayCount_ThrowsArgumentExce ICollection collection = GenericICollectionFactory(count); T[] array = new T[count]; if (count > 0) - Assert.Throws(() => collection.CopyTo(array, count)); + Assert.ThrowsAny(() => collection.CopyTo(array, count)); else collection.CopyTo(array, count); // does nothing since the array is empty } @@ -513,7 +515,7 @@ public void ICollection_Generic_CopyTo_NotEnoughSpaceInOffsettedArray_ThrowsArgu { ICollection collection = GenericICollectionFactory(count); T[] array = new T[count]; - Assert.Throws(() => collection.CopyTo(array, 1)); + Assert.ThrowsAny(() => collection.CopyTo(array, 1)); } } diff --git a/tests/J2N.Tests.xUnit/Collections/ISet.Generic.Tests.cs b/tests/J2N.Tests.xUnit/Collections/ISet.Generic.Tests.cs index dd21772b..3d3381f9 100644 --- a/tests/J2N.Tests.xUnit/Collections/ISet.Generic.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/ISet.Generic.Tests.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using J2N.Collections.Generic; using System; @@ -305,38 +304,58 @@ private void Validate_UnionWith(ISet set, IEnumerable enumerable) public void ISet_Generic_NullEnumerableArgument(int count) { ISet set = GenericISetFactory(count); - Assert.Throws(() => set.ExceptWith(null)); - Assert.Throws(() => set.IntersectWith(null)); Assert.Throws(() => set.IsProperSubsetOf(null)); Assert.Throws(() => set.IsProperSupersetOf(null)); Assert.Throws(() => set.IsSubsetOf(null)); Assert.Throws(() => set.IsSupersetOf(null)); Assert.Throws(() => set.Overlaps(null)); Assert.Throws(() => set.SetEquals(null)); - Assert.Throws(() => set.SymmetricExceptWith(null)); - Assert.Throws(() => set.UnionWith(null)); + if (!IsReadOnly) + { + Assert.Throws(() => set.ExceptWith(null)); + Assert.Throws(() => set.IntersectWith(null)); + Assert.Throws(() => set.SymmetricExceptWith(null)); + Assert.Throws(() => set.UnionWith(null)); + } + else + { + Assert.Throws(() => set.Add(CreateT(0))); + Assert.Throws(() => set.ExceptWith(null)); + Assert.Throws(() => set.IntersectWith(null)); + Assert.Throws(() => set.SymmetricExceptWith(null)); + Assert.Throws(() => set.UnionWith(null)); + } } + public static IEnumerable SetTestData() => + new[] { EnumerableType.HashSet, EnumerableType.List }.SelectMany(GetEnumerableTestData); + [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_ExceptWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_ExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_ExceptWith(set, enumerable); + } } [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_IntersectWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_IntersectWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_IntersectWith(set, enumerable); + } } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsProperSubsetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -345,7 +364,7 @@ public void ISet_Generic_IsProperSubsetOf(EnumerableType enumerableType, int set } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsProperSupersetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -354,7 +373,7 @@ public void ISet_Generic_IsProperSupersetOf(EnumerableType enumerableType, int s } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsSubsetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -363,7 +382,7 @@ public void ISet_Generic_IsSubsetOf(EnumerableType enumerableType, int setLength } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsSupersetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -372,7 +391,7 @@ public void ISet_Generic_IsSupersetOf(EnumerableType enumerableType, int setLeng } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_Overlaps(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -381,7 +400,7 @@ public void ISet_Generic_Overlaps(EnumerableType enumerableType, int setLength, } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_SetEquals(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -393,18 +412,24 @@ public void ISet_Generic_SetEquals(EnumerableType enumerableType, int setLength, [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_SymmetricExceptWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_SymmetricExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_SymmetricExceptWith(set, enumerable); + } } [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_UnionWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_UnionWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_UnionWith(set, enumerable); + } } #endregion @@ -415,8 +440,11 @@ public void ISet_Generic_UnionWith(EnumerableType enumerableType, int setLength, [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_ExceptWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_ExceptWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_ExceptWith(set, set); + } } [Theory] @@ -424,8 +452,11 @@ public void ISet_Generic_ExceptWith_Itself(int setLength) //[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Full framework throws InvalidOperationException")] public void ISet_Generic_IntersectWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_IntersectWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_IntersectWith(set, set); + } } [Theory] @@ -480,16 +511,22 @@ public void ISet_Generic_SetEquals_Itself(int setLength) [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_SymmetricExceptWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_SymmetricExceptWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_SymmetricExceptWith(set, set); + } } [Theory] [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_UnionWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_UnionWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_UnionWith(set, set); + } } #endregion @@ -500,18 +537,24 @@ public void ISet_Generic_UnionWith_Itself(int setLength) //[OuterLoop] public void ISet_Generic_ExceptWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_ExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_ExceptWith(set, enumerable); + } } [Fact] //[OuterLoop] public void ISet_Generic_IntersectWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_IntersectWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_IntersectWith(set, enumerable); + } } [Fact] @@ -572,18 +615,24 @@ public void ISet_Generic_SetEquals_LargeSet() //[OuterLoop] public void ISet_Generic_SymmetricExceptWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_SymmetricExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_SymmetricExceptWith(set, enumerable); + } } [Fact] //[OuterLoop] public void ISet_Generic_UnionWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_UnionWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_UnionWith(set, enumerable); + } } #endregion @@ -594,25 +643,28 @@ public void ISet_Generic_UnionWith_LargeSet() [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_SymmetricExceptWith_AfterRemovingElements(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - T value = CreateT(532); - if (!set.Contains(value)) - set.Add(value); - set.Remove(value); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Debug.Assert(enumerable != null); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + T value = CreateT(532); + if (!set.Contains(value)) + set.Add(value); + set.Remove(value); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Debug.Assert(enumerable != null); - IEqualityComparer comparer = GetIEqualityComparer(); - System.Collections.Generic.HashSet expected = new System.Collections.Generic.HashSet(comparer); - foreach (T element in enumerable) - if (!set.Contains(element, comparer)) - expected.Add(element); - foreach (T element in set) - if (!enumerable.Contains(element, comparer)) - expected.Add(element); - set.SymmetricExceptWith(enumerable); - Assert.Equal(expected.Count, set.Count); - Assert.True(expected.SetEquals(set)); + IEqualityComparer comparer = GetIEqualityComparer(); + System.Collections.Generic.HashSet expected = new System.Collections.Generic.HashSet(comparer); + foreach (T element in enumerable) + if (!set.Contains(element, comparer)) + expected.Add(element); + foreach (T element in set) + if (!enumerable.Contains(element, comparer)) + expected.Add(element); + set.SymmetricExceptWith(enumerable); + Assert.Equal(expected.Count, set.Count); + Assert.True(expected.SetEquals(set)); + } } #endregion From 8e4e6e88e0bad9fdb8f3ead3a9974fdf1f1df7a0 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 15:18:52 +0700 Subject: [PATCH 14/20] SWEEP: Upgraded test targets from net461 > net471, net452 > net47 because the new test adapter (the only thing compatible with .NET 8) doesn't seem to support anything lower than net462. Updated test target vs target under test documentation. --- .build/TestTargetFramework.props | 19 ++++++++------ ...ish-test-results-for-target-frameworks.yml | 6 ++--- Directory.Build.targets | 8 +++--- azure-pipelines.yml | 26 +++++++++---------- tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj | 4 +-- tests/J2N.Tests/J2N.Tests.csproj | 4 +-- 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/.build/TestTargetFramework.props b/.build/TestTargetFramework.props index ee77308b..ab4e1195 100644 --- a/.build/TestTargetFramework.props +++ b/.build/TestTargetFramework.props @@ -4,12 +4,13 @@ - - + + + true - net8.0;net6.0;net5.0;net48;net472;net461;net452 + net8.0;net6.0;net5.0;net48;net472;net471;net47 - TargetFramework=net40 - TargetFramework=netstandard2.0 + TargetFramework=net40 + TargetFramework=netstandard2.0 TargetFramework=net45 diff --git a/.build/azure-templates/publish-test-results-for-target-frameworks.yml b/.build/azure-templates/publish-test-results-for-target-frameworks.yml index 04196a29..332e4fbd 100644 --- a/.build/azure-templates/publish-test-results-for-target-frameworks.yml +++ b/.build/azure-templates/publish-test-results-for-target-frameworks.yml @@ -76,7 +76,7 @@ steps: - template: publish-test-results.yml parameters: - framework: 'net461' + framework: 'net471' testProjectName: '${{ parameters.testProjectName }}' osName: '${{ parameters.osName }}' testPlatform: '${{ parameters.testPlatform }}' @@ -84,11 +84,11 @@ steps: testResultsArtifactName: '${{ parameters.testResultsArtifactName }}' testResultsFileName: '${{ parameters.testResultsFileName }}' -# Special Case: Using lowest supported version of .NET Framework (4.5.2) in xUnit to test .NET Framework 4.0. +# Special Case: Using a supported version of .NET Framework (4.7) in xUnit to test .NET Framework 4.0. # This only works because we are using the SetTargetFramework attribute to set the target framework of the project reference. - template: publish-test-results.yml parameters: - framework: 'net452' + framework: 'net47' testProjectName: '${{ parameters.testProjectName }}' osName: '${{ parameters.osName }}' testPlatform: '${{ parameters.testPlatform }}' diff --git a/Directory.Build.targets b/Directory.Build.targets index e3462f48..733eedd1 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -130,8 +130,8 @@ - - + + $(DefineConstants);FEATURE_SERIALIZABLE_RANDOM $(DefineConstants);FEATURE_SERIALIZABLE_STRINGS @@ -146,8 +146,8 @@ full - - + + $(DefineConstants);FEATURE_EXCEPTIONDISPATCHINFO $(DefineConstants);FEATURE_IREADONLYCOLLECTIONS diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6259e363..ee34c2a0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,7 +23,7 @@ name: 'vNext$(rev:.r)' # Format for build number (will be overridden) variables: - name: TestTargetFrameworks - value: 'net8.0;net6.0;net5.0;net48;net472;net461;net452' + value: 'net8.0;net6.0;net5.0;net48;net472;net471;net47' - name: DotNetSDKVersion value: '8.0.401' - name: BinaryArtifactName @@ -323,61 +323,61 @@ stages: maximumAllowedFailures: 0 # Maximum allowed failures for a successful build dotNetSdkVersion: '$(DotNetSDKVersion)' - - job: Test_net461_x64 + - job: Test_net471_x64 condition: and(succeeded(), ne(variables['RunTests'], 'false')) - displayName: 'Test net461,x64 on Windows' + displayName: 'Test net4671,x64 on Windows' pool: vmImage: 'windows-latest' steps: - template: '.build/azure-templates/run-tests-on-os.yml' parameters: osName: 'Windows' - testTargetFrameworks: 'net461' + testTargetFrameworks: 'net471' vsTestPlatform: 'x64' testResultsArtifactName: '$(TestResultsArtifactName)' maximumAllowedFailures: 0 # Maximum allowed failures for a successful build dotNetSdkVersion: '$(DotNetSDKVersion)' - - job: Test_net461_x86 # Only run if explicitly enabled with RunX86Tests + - job: Test_net471_x86 # Only run if explicitly enabled with RunX86Tests condition: and(succeeded(), ne(variables['RunTests'], 'false'), eq(variables['RunX86Tests'], 'true')) - displayName: 'Test net461,x86 on Windows' + displayName: 'Test net471,x86 on Windows' pool: vmImage: 'windows-latest' steps: - template: '.build/azure-templates/run-tests-on-os.yml' parameters: osName: 'Windows' - testTargetFrameworks: 'net461' + testTargetFrameworks: 'net471' vsTestPlatform: 'x86' testResultsArtifactName: '$(TestResultsArtifactName)' maximumAllowedFailures: 0 # Maximum allowed failures for a successful build dotNetSdkVersion: '$(DotNetSDKVersion)' - - job: Test_net452_x64 + - job: Test_net47_x64 condition: and(succeeded(), ne(variables['RunTests'], 'false')) - displayName: 'Test net452,x64 on Windows' + displayName: 'Test net47,x64 on Windows' pool: vmImage: 'windows-latest' steps: - template: '.build/azure-templates/run-tests-on-os.yml' parameters: osName: 'Windows' - testTargetFrameworks: 'net452' + testTargetFrameworks: 'net47' vsTestPlatform: 'x64' testResultsArtifactName: '$(TestResultsArtifactName)' maximumAllowedFailures: 0 # Maximum allowed failures for a successful build dotNetSdkVersion: '$(DotNetSDKVersion)' - - job: Test_net452_x86 # Only run if explicitly enabled with RunX86Tests + - job: Test_net47_x86 # Only run if explicitly enabled with RunX86Tests condition: and(succeeded(), ne(variables['RunTests'], 'false'), eq(variables['RunX86Tests'], 'true')) - displayName: 'Test net452,x86 on Windows' + displayName: 'Test net47,x86 on Windows' pool: vmImage: 'windows-latest' steps: - template: '.build/azure-templates/run-tests-on-os.yml' parameters: osName: 'Windows' - testTargetFrameworks: 'net452' + testTargetFrameworks: 'net47' vsTestPlatform: 'x86' testResultsArtifactName: '$(TestResultsArtifactName)' maximumAllowedFailures: 0 # Maximum allowed failures for a successful build diff --git a/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj b/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj index 3a5950da..2fe3775a 100644 --- a/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj +++ b/tests/J2N.Tests.xUnit/J2N.Tests.xUnit.csproj @@ -20,8 +20,8 @@ - - - - Date: Mon, 16 Sep 2024 15:43:11 +0700 Subject: [PATCH 15/20] J2N.Numerics: Suppressed build warnings in Microsoft-sourced code that hasn't changed in .NET 8 --- src/J2N/Numerics/DotNetNumber.BigInteger.cs | 14 ++++++++++++++ src/J2N/Numerics/DotNetNumber.Dragon4.cs | 12 ++++++++++++ .../DotNetNumber.NumberToFloatingPointBits.cs | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/src/J2N/Numerics/DotNetNumber.BigInteger.cs b/src/J2N/Numerics/DotNetNumber.BigInteger.cs index 8ffe1770..7eba5780 100644 --- a/src/J2N/Numerics/DotNetNumber.BigInteger.cs +++ b/src/J2N/Numerics/DotNetNumber.BigInteger.cs @@ -558,14 +558,18 @@ public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger if (digit > 0) { // Now it's time to subtract our current quotient +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope uint carry = SubtractDivisor(ref rem, n, ref rhs, digit); +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope if (carry != t) { Debug.Assert(carry == t + 1); // Our guess was still exactly one too high +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope carry = AddDivisor(ref rem, n, ref rhs); +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope digit--; Debug.Assert(carry == 1); @@ -870,7 +874,9 @@ public static void Pow10(uint exponent, out BigInteger result) fixed (uint* pBigNumEntry = &s_Pow10BigNumTable[s_Pow10BigNumTableIndices[index]]) { ref BigInteger rhs = ref *(BigInteger*)(pBigNumEntry); +#pragma warning disable CS9082 // Local is returned by reference but was initialized to a value that cannot be returned by reference Multiply(ref lhs, ref rhs, out product); +#pragma warning restore CS9082 // Local is returned by reference but was initialized to a value that cannot be returned by reference } // Swap to the next temporary @@ -884,7 +890,9 @@ public static void Pow10(uint exponent, out BigInteger result) exponent >>= 1; } +#pragma warning disable CS9082 // Local is returned by reference but was initialized to a value that cannot be returned by reference SetValue(out result, ref lhs); +#pragma warning restore CS9082 // Local is returned by reference but was initialized to a value that cannot be returned by reference } private static uint AddDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInteger rhs) @@ -1025,19 +1033,25 @@ public bool IsZero() public void Multiply(uint value) { +#pragma warning disable CS9084 // Struct member returns 'this' or other instance members by reference Multiply(ref this, value, out this); +#pragma warning restore CS9084 // Struct member returns 'this' or other instance members by reference } public void Multiply(ref BigInteger value) { if (value._length <= 1) { +#pragma warning disable CS9084 // Struct member returns 'this' or other instance members by reference Multiply(ref this, value.ToUInt32(), out this); +#pragma warning restore CS9084 // Struct member returns 'this' or other instance members by reference } else { SetValue(out BigInteger temp, ref this); +#pragma warning disable CS9080, CS9091, CS9094 Multiply(ref temp, ref value, out this); +#pragma warning restore CS9080, CS9091, CS9094 } } diff --git a/src/J2N/Numerics/DotNetNumber.Dragon4.cs b/src/J2N/Numerics/DotNetNumber.Dragon4.cs index 82f4c09a..08a81fc4 100644 --- a/src/J2N/Numerics/DotNetNumber.Dragon4.cs +++ b/src/J2N/Numerics/DotNetNumber.Dragon4.cs @@ -254,7 +254,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi if (pScaledMarginHigh != &scaledMarginLow) { +#pragma warning disable CS9091 // This returns local by reference but it is not a ref local BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); +#pragma warning restore CS9091 // This returns local by reference but it is not a ref local } } @@ -268,7 +270,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // shorter strings for various edge case values like 1.23E+22 BigInteger.Add(ref scaledValue, ref *pScaledMarginHigh, out BigInteger scaledValueHigh); +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope int cmpHigh = BigInteger.Compare(ref scaledValueHigh, ref scale); +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope estimateTooLow = isEven ? (cmpHigh >= 0) : (cmpHigh > 0); } else @@ -292,7 +296,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi if (pScaledMarginHigh != &scaledMarginLow) { +#pragma warning disable CS9091 // This returns local by reference but it is not a ref local BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); +#pragma warning restore CS9091 // This returns local by reference but it is not a ref local } } @@ -351,7 +357,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi if (pScaledMarginHigh != &scaledMarginLow) { +#pragma warning disable CS9091 // This returns local by reference but it is not a ref local BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); +#pragma warning restore CS9091 // This returns local by reference but it is not a ref local } } @@ -379,7 +387,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // stop looping if we are far enough away from our neighboring values or if we have reached the cutoff digit int cmpLow = BigInteger.Compare(ref scaledValue, ref scaledMarginLow); +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope int cmpHigh = BigInteger.Compare(ref scaledValueHigh, ref scale); +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope if (isEven) { @@ -407,7 +417,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi if (pScaledMarginHigh != &scaledMarginLow) { +#pragma warning disable CS9091 // This returns local by reference but it is not a ref local BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); +#pragma warning restore CS9091 // This returns local by reference but it is not a ref local } digitExponent--; diff --git a/src/J2N/Numerics/DotNetNumber.NumberToFloatingPointBits.cs b/src/J2N/Numerics/DotNetNumber.NumberToFloatingPointBits.cs index 0d1ba19d..6dcdfa47 100644 --- a/src/J2N/Numerics/DotNetNumber.NumberToFloatingPointBits.cs +++ b/src/J2N/Numerics/DotNetNumber.NumberToFloatingPointBits.cs @@ -634,14 +634,18 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F // earlier, or one greater than that shift: uint fractionalExponent = fractionalShift; +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope if (BigInteger.Compare(ref fractionalNumerator, ref fractionalDenominator) < 0) { fractionalExponent++; } +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope fractionalNumerator.ShiftLeft(remainingBitsOfPrecisionRequired); +#pragma warning disable CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope BigInteger.DivRem(ref fractionalNumerator, ref fractionalDenominator, out BigInteger bigFractionalMantissa, out BigInteger fractionalRemainder); +#pragma warning restore CS9080 // Use of variable in this context may expose referenced variables outside of their declaration scope ulong fractionalMantissa = bigFractionalMantissa.ToUInt64(); bool hasZeroTail = !number.HasNonZeroTail && fractionalRemainder.IsZero(); From f8cc6d689d01656a0c8daf27de6e14f91239c70c Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 16:10:25 +0700 Subject: [PATCH 16/20] run-tests-on-os.yml: Run test projects in the background in parallel --- .build/azure-templates/run-tests-on-os.yml | 81 +++++++++++++--------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/.build/azure-templates/run-tests-on-os.yml b/.build/azure-templates/run-tests-on-os.yml index b6ed42b1..246f556b 100644 --- a/.build/azure-templates/run-tests-on-os.yml +++ b/.build/azure-templates/run-tests-on-os.yml @@ -143,38 +143,43 @@ steps: function RunTests([string]$framework, [string]$fileRegexPattern) { if (!(IsSupportedFramework($framework))) { continue } - + $testBinaries = Get-ChildItem -Path "$testBinaryRootDirectory" -File -Recurse | Where-Object {$_.FullName -match "$framework"} | Where-Object {$_.FullName -match "$fileRegexPattern"} | Select -ExpandProperty FullName foreach ($testBinary in $testBinaries) { $testName = [System.IO.Path]::GetFileNameWithoutExtension($testBinary) - - # Pause if we have queued too many parallel jobs - #$running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) - #if ($running.Count -ge $maximumParalellJobs) { - # Write-Host "" - # Write-Host " Running tests in parallel on $($running.Count) projects." -ForegroundColor Cyan - # Write-Host " Next in queue is $projectName on $framework. This will take a bit, please wait..." -ForegroundColor Cyan - # $running | Wait-Job -Any | Out-Null - #} - + + if ($maximumParalellJobs -gt 1) { + # Pause if we have queued too many parallel jobs + $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) + if ($running.Count -ge $maximumParalellJobs) { + Write-Host "" + Write-Host " Running tests in parallel on $($running.Count) projects." -ForegroundColor Cyan + Write-Host " Next in queue is $projectName on $framework. This will take a bit, please wait..." -ForegroundColor Cyan + $running | Wait-Job -Any | Out-Null + } + } + $testResultDirectory = "$testResultsArtifactDirectory/$testOSName/$framework/$testPlatform/$testName" if (!(Test-Path "$testResultDirectory")) { New-Item "$testResultDirectory" -ItemType Directory -Force } - + Write-Host "Testing '$testBinary' on framework '$framework' and outputting test results to '$testResultDirectory/${{ parameters.testResultsFileName }}'..." - dotnet test "$testBinary" --framework "$framework" --blame --no-build --no-restore --logger:"console;verbosity=normal" --logger:"trx;LogFileName=${{ parameters.testResultsFileName }}" --results-directory:"$testResultDirectory" --blame-hang-timeout 10minutes --blame-hang-dump-type mini -- RunConfiguration.TargetPlatform=$testPlatform - - - $testExpression = "$testExpression -- RunConfiguration.TargetPlatform=$testPlatform" - - #$scriptBlock = { - # param([string]$testBinary, [string]$fwork, [string]$testPlatform, [string]$testResultDirectory) - # dotnet test "$testBinary" --framework "$framework" --blame --no-build --no-restore --logger:"console;verbosity=normal" --logger:"trx;LogFileName=${{ parameters.testResultsFileName }}" --results-directory:"$testResultDirectory" --blame-hang-timeout 10minutes --blame-hang-dump-type mini -- RunConfiguration.TargetPlatform=$testPlatform > "$testResultDirectory/dotnet-vstest.log" 2> "$testResultDirectory/dotnet-vstest-error.log" - #} - - # Execute the jobs in parallel - #Start-Job $scriptBlock -ArgumentList $testBinary,$fwork,$testPlatform,$testResultDirectory + if ($maximumParalellJobs -le 1) { + dotnet test "$testBinary" --framework "$framework" --blame --no-build --no-restore --logger:"console;verbosity=normal" --logger:"trx;LogFileName=${{ parameters.testResultsFileName }}" --results-directory:"$testResultDirectory" --blame-hang-timeout 10minutes --blame-hang-dump-type mini -- RunConfiguration.TargetPlatform=$testPlatform + } else { + + $scriptBlock = { + param([string]$testName, [string]$testBinary, [string]$framework, [string]$testPlatform, [string]$testResultDirectory) + dotnet test "$testBinary" --framework "$framework" --blame --no-build --no-restore --logger:"console;verbosity=normal" --logger:"trx;LogFileName=${{ parameters.testResultsFileName }}" --results-directory:"$testResultDirectory" --blame-hang-timeout 10minutes --blame-hang-dump-type mini -- RunConfiguration.TargetPlatform=$testPlatform > "$testResultDirectory/dotnet-vstest.log" 2> "$testResultDirectory/dotnet-vstest-error.log" + } + + # Avoid dotnet test collisions by delaying for 500ms + Start-Sleep -Milliseconds 500 + + # Execute the jobs in parallel + Start-Job -Name "$testName,$framework,$testPlatform" -ScriptBlock $scriptBlock -ArgumentList $testName,$testBinary,$framework,$testPlatform,$testResultDirectory + } } } @@ -182,15 +187,23 @@ steps: RunTests -Framework "$framework" -FileRegexPattern "$testBinaryFilesPattern" } - # Wait for it all to complete - #do { - # $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) - # if ($running.Count -gt 0) { - # Write-Host "" - # Write-Host " Almost finished, only $($running.Count) projects left..." -ForegroundColor Cyan - # $running | Wait-Job -Any - # } - #} until ($running.Count -eq 0) + if ($maximumParalellJobs -gt 1) { + # Wait for it all to complete + do { + $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) + if ($running.Count -gt 0) { + Write-Host "" + Write-Host " Almost finished, only $($running.Count) projects left..." -ForegroundColor Cyan + [int]$number = 0 + foreach ($runningJob in $running) { + $number++ + $jobName = $runningJob | Select -ExpandProperty Name + Write-Host "$number. $jobName" + } + $running | Wait-Job -Any + } + } until ($running.Count -eq 0) + } $global:LASTEXITCODE = 0 # Force the script to continue on error displayName: 'dotnet test ${{ parameters.testTargetFrameworks }}' @@ -210,7 +223,7 @@ steps: # Publish Test Results task or the (deprecated) TfsPublisher # our only other option is to make a task for every supported # platform and project and update it whenever a new platform -# is targeted or test project is created in ICU4N. +# is targeted or test project is created in J2N. - template: 'publish-test-results-for-test-projects.yml' parameters: From 61d72c49b4b2e674cea251b7b7207a0bb2235883 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 17:05:11 +0700 Subject: [PATCH 17/20] run-tests-on-os.yml: Use .NET 6 SDK for testing .NET Framework, since .NET 8 is too slow --- .build/azure-templates/run-tests-on-os.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.build/azure-templates/run-tests-on-os.yml b/.build/azure-templates/run-tests-on-os.yml index 246f556b..cc8467be 100644 --- a/.build/azure-templates/run-tests-on-os.yml +++ b/.build/azure-templates/run-tests-on-os.yml @@ -34,26 +34,35 @@ steps: displayName: 'Validate Template Parameters' - pwsh: | + $testTargetFrameworks = '${{ parameters.testTargetFrameworks }}' $testPlatform = '${{ parameters.vsTestPlatform }}' + $dotnetSdkVersion = '${{ parameters.dotNetSdkVersion }}' if ($IsWindows -eq $null) { $IsWindows = $env:OS.StartsWith('Win') } + # .NET Framework tests are SLOW when using the .NET 8 SDK (especially those for net47 targeting net40). + # So, we use .NET 6 SDK instead. + if ($testTargetFrameworks.StartsWith('net4')) { + $dotnetSdkVersion = '6.0.421' + } + Write-Host "Using SDK Version: $dotnetSdkVersion" $performMulitLevelLookup = if ($IsWindows -and $testPlatform.Equals('x86')) { 'true' } else { 'false' } Write-Host "##vso[task.setvariable variable=PerformMultiLevelLookup;]$performMulitLevelLookup" + Write-Host "##vso[task.setvariable variable=DotNetSdkVersion;]$dotnetSdkVersion" #- template: 'show-all-environment-variables.yml' # Uncomment for debugging - template: 'install-dotnet-sdk.yml' parameters: - sdkVersion: '${{ parameters.dotNetSdkVersion }}' - performMultiLevelLookup: '${{ variables.PerformMultiLevelLookup }}' + sdkVersion: '$(DotNetSdkVersion)' + performMultiLevelLookup: '$(PerformMultiLevelLookup)' # Hack: .NET 8 no longer installs the x86 bits and they must be installed separately. However, it is not # trivial to get it into the path and to get it to pass the minimum SDK version check in runbuild.ps1. # So, we install it afterward and set the environment variable so the above SDK can delegate to it. # This code only works on Windows. - pwsh: | - $sdkVersion = '${{ parameters.dotNetSdkVersion }}' + $sdkVersion = '$(DotNetSdkVersion)' $architecture = '${{ parameters.vsTestPlatform }}' $installScriptPath = "${env:AGENT_TEMPDIRECTORY}/dotnet-install.ps1" $installScriptUrl = "https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1" @@ -62,7 +71,7 @@ steps: $installPath = "${env:ProgramFiles(x86)}/dotnet" & $installScriptPath -Version $sdkVersion -Architecture $architecture -InstallDir $installPath Write-Host "##vso[task.setvariable variable=DOTNET_ROOT_X86;]$installPath" - displayName: 'Use .NET SDK ${{ parameters.dotNetSdkVersion }} (x86)' + displayName: 'Use .NET SDK $(DotNetSdkVersion) (x86)' condition: and(succeeded(), contains('${{ parameters.testTargetFrameworks }}', 'net8.'), eq('${{ parameters.vsTestPlatform }}', 'x86')) - task: UseDotNet@2 From ebce77634617593b11a81d11f57be21d9aed0f37 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 21:07:11 +0700 Subject: [PATCH 18/20] azure-pipelines.yml: Fixed display name for net471 x64 job --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ee34c2a0..58731280 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -325,7 +325,7 @@ stages: - job: Test_net471_x64 condition: and(succeeded(), ne(variables['RunTests'], 'false')) - displayName: 'Test net4671,x64 on Windows' + displayName: 'Test net471,x64 on Windows' pool: vmImage: 'windows-latest' steps: From 4b8f49b6daccf431f59211922a5e6909c1bda9a4 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 16 Sep 2024 21:46:24 +0700 Subject: [PATCH 19/20] J2N.Tests.xUnit (SubList_Tests): removed commented code --- .../Generic/Extensions/SubList/SubList.Tests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs b/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs index 7503eab4..76dcf52c 100644 --- a/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs +++ b/tests/J2N.Tests.xUnit/Collections/Generic/Extensions/SubList/SubList.Tests.cs @@ -93,13 +93,6 @@ protected override SCG.IList GenericIListFactory() public abstract class SubList_Tests : IList_Generic_Tests { -// // J2N: See the comment in the root Directory.Build.targets file -//#if FEATURE_READONLYCOLLECTION_ENUMERATOR_EMPTY_CURRENT_UNDEFINEDOPERATION_DOESNOTTHROW -// protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; -//#else -// //protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; -//#endif - private SCG.List OriginalList { get; set; } #region IList Helper Methods From 9faac89cda614574622fd6017b7526c398b081bf Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 17 Sep 2024 09:05:25 +0700 Subject: [PATCH 20/20] J2N.TestCharacter::Test_getType_I(): Added comment to indicate this character definition changed in .NET 8 --- tests/J2N.Tests/TestCharacter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/J2N.Tests/TestCharacter.cs b/tests/J2N.Tests/TestCharacter.cs index 52e89c1a..5420e686 100644 --- a/tests/J2N.Tests/TestCharacter.cs +++ b/tests/J2N.Tests/TestCharacter.cs @@ -1990,7 +1990,7 @@ public void Test_getType_I() assertTrue(Character.GetType((int)'\u2029') == UnicodeCategory.ParagraphSeparator); #if FEATURE_UNICODE_DEFINED_0x9FFF - assertTrue(Character.GetType(0x9FFF) == UnicodeCategory.OtherLetter); + assertTrue(Character.GetType(0x9FFF) == UnicodeCategory.OtherLetter); // This character is now defined in .NET 8 #else assertTrue(Character.GetType(0x9FFF) == UnicodeCategory.OtherNotAssigned); #endif