diff --git a/DotNetApiDiff.sln b/DotNetApiDiff.sln index 800275a..739e04a 100644 --- a/DotNetApiDiff.sln +++ b/DotNetApiDiff.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -6,6 +6,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetApiDiff", "src\DotNet EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetApiDiff.Tests", "tests\DotNetApiDiff.Tests\DotNetApiDiff.Tests.csproj", "{B2C3D4E5-F6G7-8901-BCDE-F23456789012}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9E9A40DF-93D7-4C97-A4B2-EFD48382576D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAssemblyV1", "tests\TestAssemblies\TestAssemblyV1.csproj", "{5E8D074B-6E7B-4730-BBBE-4E81841DA8D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAssemblyV2", "tests\TestAssemblies\TestAssemblyV2.csproj", "{4EAC9034-1137-4E2A-A70B-D8CEB248F107}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,5 +26,17 @@ Global {B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Release|Any CPU.Build.0 = Release|Any CPU + {5E8D074B-6E7B-4730-BBBE-4E81841DA8D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E8D074B-6E7B-4730-BBBE-4E81841DA8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E8D074B-6E7B-4730-BBBE-4E81841DA8D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E8D074B-6E7B-4730-BBBE-4E81841DA8D2}.Release|Any CPU.Build.0 = Release|Any CPU + {4EAC9034-1137-4E2A-A70B-D8CEB248F107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EAC9034-1137-4E2A-A70B-D8CEB248F107}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EAC9034-1137-4E2A-A70B-D8CEB248F107}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EAC9034-1137-4E2A-A70B-D8CEB248F107}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5E8D074B-6E7B-4730-BBBE-4E81841DA8D2} = {9E9A40DF-93D7-4C97-A4B2-EFD48382576D} + {4EAC9034-1137-4E2A-A70B-D8CEB248F107} = {9E9A40DF-93D7-4C97-A4B2-EFD48382576D} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal diff --git a/Taskfile.yml b/Taskfile.yml index 0d86e6c..2186927 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -7,6 +7,7 @@ vars: SOLUTION: DotNetApiDiff.sln PROJECT: src/DotNetApiDiff/DotNetApiDiff.csproj TEST_PROJECT: tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj + TEST_ASSEMBLIES_DIR: tests/TestAssemblies COVERAGE_DIR: coverage-report TEST_REPORT_DIR: test-report @@ -21,11 +22,25 @@ tasks: cmds: - dotnet build {{.SOLUTION}} --configuration Release + build:test-assemblies: + desc: Build test assemblies and copy to TestData + cmds: + - cd {{.TEST_ASSEMBLIES_DIR}} && ./build-test-assemblies.sh + + clean:test-assemblies: + desc: Clean test assemblies build outputs + cmds: + - dotnet clean {{.TEST_ASSEMBLIES_DIR}}/TestAssemblyV1.csproj + - dotnet clean {{.TEST_ASSEMBLIES_DIR}}/TestAssemblyV2.csproj + - rm -f tests/DotNetApiDiff.Tests/TestData/TestAssemblyV1.dll + - rm -f tests/DotNetApiDiff.Tests/TestData/TestAssemblyV2.dll + clean: desc: Clean build outputs cmds: - dotnet clean {{.SOLUTION}} - task: clean:coverage + - task: clean:test-assemblies clean:coverage: desc: Clean coverage reports @@ -49,6 +64,7 @@ tasks: test:integration: desc: Run integration tests only + deps: [build:test-assemblies] cmds: - dotnet test {{.TEST_PROJECT}} --filter "Category=Integration" --configuration Release @@ -213,5 +229,6 @@ tasks: - task: restore - task: code:quality - task: build + - task: build:test-assemblies - task: test - task: coverage diff --git a/tests/DotNetApiDiff.Tests/ApiExtraction/ApiComparerManualTests.cs b/tests/DotNetApiDiff.Tests/ApiExtraction/ApiComparerManualTests.cs index 05d59c6..4c46945 100644 --- a/tests/DotNetApiDiff.Tests/ApiExtraction/ApiComparerManualTests.cs +++ b/tests/DotNetApiDiff.Tests/ApiExtraction/ApiComparerManualTests.cs @@ -1,75 +1,294 @@ -// Copyright DotNet API Diff Project Contributors - SPDX Identifier: MIT +using System.Reflection; using DotNetApiDiff.ApiExtraction; +using DotNetApiDiff.AssemblyLoading; using DotNetApiDiff.Interfaces; using DotNetApiDiff.Models; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using DotNetApiDiff.Models.Configuration; using Moq; using Xunit; +using Xunit.Abstractions; -namespace DotNetApiDiff.Tests.ApiExtraction; - -public class ApiComparerManualTests +namespace DotNetApiDiff.Tests.ApiExtraction { - [Fact] - public void CompareMembers_DetectsRemovedMembers_ManualTest() + /// + /// Integration tests using manually created test assemblies + /// + public class ApiComparerManualTests { - // Arrange - Console.WriteLine("Starting CompareMembers_DetectsRemovedMembers_ManualTest"); + private readonly string _testAssemblyV1Path; + private readonly string _testAssemblyV2Path; + private readonly IAssemblyLoader _assemblyLoader; + private readonly IApiExtractor _apiExtractor; + private readonly INameMapper _nameMapper; + private readonly IDifferenceCalculator _differenceCalculator; + private readonly IChangeClassifier _changeClassifier; + private readonly IApiComparer _apiComparer; + private readonly ITestOutputHelper _output; + + public ApiComparerManualTests(ITestOutputHelper output) + { + _output = output; + + // For the purpose of this test, we'll use the test assemblies from the TestData directory + string testDataDir = Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, + "TestData"); + + _testAssemblyV1Path = Path.Combine(testDataDir, "TestAssemblyV1.dll"); + _testAssemblyV2Path = Path.Combine(testDataDir, "TestAssemblyV2.dll"); + + _output.WriteLine($"Test assembly V1 path: {_testAssemblyV1Path}"); + _output.WriteLine($"Test assembly V2 path: {_testAssemblyV2Path}"); + + // Skip tests if the test assemblies don't exist + if (!File.Exists(_testAssemblyV1Path) || !File.Exists(_testAssemblyV2Path)) + { + _output.WriteLine("Test assemblies not found. Skipping tests."); + return; + } + + // Create the real components for integration testing + _assemblyLoader = new AssemblyLoader(); + _apiExtractor = new ApiExtractor(new TypeAnalyzer(new MemberSignatureBuilder())); + _nameMapper = new NameMapper(); + _differenceCalculator = new DifferenceCalculator(); + _changeClassifier = new ChangeClassifier(); + _apiComparer = new ApiComparer( + _assemblyLoader, + _apiExtractor, + _nameMapper, + _differenceCalculator, + _changeClassifier); + + // Create the real components for integration testing + _assemblyLoader = new AssemblyLoader(); + _apiExtractor = new ApiExtractor(new TypeAnalyzer(new MemberSignatureBuilder())); + _nameMapper = new NameMapper(); + _differenceCalculator = new DifferenceCalculator(); + _changeClassifier = new ChangeClassifier(); + _apiComparer = new ApiComparer( + _assemblyLoader, + _apiExtractor, + _nameMapper, + _differenceCalculator, + _changeClassifier); + } + + [Fact] + public void Compare_WithTestAssemblies_DetectsBasicChanges() + { + // Skip test if assemblies don't exist + if (!File.Exists(_testAssemblyV1Path) || !File.Exists(_testAssemblyV2Path)) + { + _output.WriteLine("Test assemblies not found. Skipping test."); + return; + } + + // Arrange + var config = new ComparisonConfiguration(); + + // Act + var result = _apiComparer.Compare(_testAssemblyV1Path, _testAssemblyV2Path, config); + + // Assert + Assert.NotNull(result); + + // Verify additions + Assert.Contains(result.Additions, change => + change.TargetMember.Name == "NewMethod" && + change.TargetMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Additions, change => + change.TargetMember.Name == "NewProperty" && + change.TargetMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Additions, change => + change.TargetMember.Name == "NewField" && + change.TargetMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Additions, change => + change.TargetMember.Name == "NewEvent" && + change.TargetMember.DeclaringType == "TestAssembly.PublicClass"); + + // Verify removals + Assert.Contains(result.Removals, change => + change.SourceMember.Name == "RemovedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Removals, change => + change.SourceMember.Name == "RemovedProperty" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Removals, change => + change.SourceMember.Name == "RemovedField" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Removals, change => + change.SourceMember.Name == "RemovedEvent" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + // Verify modifications + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "ChangedSignatureMethod" && + change.TargetMember.Name == "ChangedSignatureMethod" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "ChangedTypeProperty" && + change.TargetMember.Name == "ChangedTypeProperty" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "VisibilityChangedMethod" && + change.TargetMember.Name == "VisibilityChangedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + } - // Create test data - use different types to ensure they are treated as distinct objects - var oldType = typeof(string); - var newType = typeof(int); // Use a different type than oldType + [Fact] + public void Compare_WithNamespaceMapping_DetectsRenamedNamespace() + { + // Skip test if assemblies don't exist + if (!File.Exists(_testAssemblyV1Path) || !File.Exists(_testAssemblyV2Path)) + { + _output.WriteLine("Test assemblies not found. Skipping test."); + return; + } + + // Arrange + var config = new ComparisonConfiguration + { + NamespaceMappings = new Dictionary> + { + { "TestAssembly.NamespaceToBeRenamed", new List { "TestAssembly.RenamedNamespace" } } + } + }; + + // Act + var result = _apiComparer.Compare(_testAssemblyV1Path, _testAssemblyV2Path, config); + + // Assert + Assert.NotNull(result); + + // Verify that the class in the renamed namespace is not reported as removed + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.DeclaringType == "TestAssembly.NamespaceToBeRenamed.ClassInRenamedNamespace"); + + // Verify that the class in the renamed namespace is not reported as added + Assert.DoesNotContain(result.Additions, change => + change.TargetMember.DeclaringType == "TestAssembly.RenamedNamespace.ClassInRenamedNamespace"); + + // Verify that the members of the class are correctly mapped + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.Name == "Method" && + change.SourceMember.DeclaringType == "TestAssembly.NamespaceToBeRenamed.ClassInRenamedNamespace"); - var oldMember = new ApiMember + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.Name == "Property" && + change.SourceMember.DeclaringType == "TestAssembly.NamespaceToBeRenamed.ClassInRenamedNamespace"); + } + + [Fact] + public void Compare_WithExclusions_ExcludesSpecifiedMembers() { - Name = "OldMethod", - FullName = "System.String.OldMethod", - Signature = "public void OldMethod()", - Type = MemberType.Method - }; - - // Create manual mocks - var mockApiExtractor = new Mock(); - mockApiExtractor.Setup(x => x.ExtractTypeMembers(oldType)) - .Returns(new List { oldMember }); - mockApiExtractor.Setup(x => x.ExtractTypeMembers(newType)) - .Returns(new List()); - - var expectedDifference = new ApiDifference + // Skip test if assemblies don't exist + if (!File.Exists(_testAssemblyV1Path) || !File.Exists(_testAssemblyV2Path)) + { + _output.WriteLine("Test assemblies not found. Skipping test."); + return; + } + + // Arrange + var config = new ComparisonConfiguration + { + ExcludedMembers = new List + { + "TestAssembly.PublicClass.RemovedMethod", + "TestAssembly.PublicClass.RemovedProperty", + "TestAssembly.IPublicInterface.RemovedMethod" + } + }; + + // Act + var result = _apiComparer.Compare(_testAssemblyV1Path, _testAssemblyV2Path, config); + + // Assert + Assert.NotNull(result); + + // Verify that excluded members are not reported as removed + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.Name == "RemovedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.Name == "RemovedProperty" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.DoesNotContain(result.Removals, change => + change.SourceMember.Name == "RemovedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.IPublicInterface"); + + // Verify that excluded members are reported in the Excluded collection + Assert.Contains(result.Excluded, change => + change.SourceMember.Name == "RemovedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Excluded, change => + change.SourceMember.Name == "RemovedProperty" && + change.SourceMember.DeclaringType == "TestAssembly.PublicClass"); + + Assert.Contains(result.Excluded, change => + change.SourceMember.Name == "RemovedMethod" && + change.SourceMember.DeclaringType == "TestAssembly.IPublicInterface"); + } + + [Fact] + public void Compare_WithBreakingChangeRules_ClassifiesBreakingChanges() { - ChangeType = ChangeType.Removed, - ElementType = ApiElementType.Method, - ElementName = "System.String.OldMethod", - Description = "Removed method 'System.String.OldMethod'", - IsBreakingChange = true - }; - - var mockDiffCalc = new Mock(); - mockDiffCalc.Setup(x => x.CalculateRemovedMember(It.Is(m => m.Signature == oldMember.Signature))) - .Returns(expectedDifference); - - var logger = new NullLogger(); - - // Create a mock NameMapper - var mockNameMapper = new Mock(); - - // Create a mock ChangeClassifier - var mockChangeClassifier = new Mock(); - mockChangeClassifier.Setup(x => x.ClassifyChange(It.IsAny())) - .Returns(diff => diff); - - // Create the comparer with our manual mocks - var apiComparer = new ApiComparer(mockApiExtractor.Object, mockDiffCalc.Object, mockNameMapper.Object, mockChangeClassifier.Object, logger); - - // Act - Console.WriteLine("About to call CompareMembers"); - var result = apiComparer.CompareMembers(oldType, newType).ToList(); - Console.WriteLine($"CompareMembers returned {result.Count} results"); - - // Assert - Assert.Single(result); - Assert.Equal(ChangeType.Removed, result[0].ChangeType); - Assert.Equal("System.String.OldMethod", result[0].ElementName); + // Skip test if assemblies don't exist + if (!File.Exists(_testAssemblyV1Path) || !File.Exists(_testAssemblyV2Path)) + { + _output.WriteLine("Test assemblies not found. Skipping test."); + return; + } + + // Arrange + var config = new ComparisonConfiguration + { + BreakingChangeRules = new BreakingChangeRules + { + TreatMemberRemovalAsBreaking = true, + TreatSignatureChangeAsBreaking = true, + TreatVisibilityDecreaseAsBreaking = true, + TreatPropertyTypeChangeAsBreaking = true + } + }; + + // Act + var result = _apiComparer.Compare(_testAssemblyV1Path, _testAssemblyV2Path, config); + + // Assert + Assert.NotNull(result); + + // Verify that removals are classified as breaking changes + foreach (var removal in result.Removals) + { + Assert.True(removal.IsBreakingChange); + } + + // Verify that signature changes are classified as breaking changes + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "ChangedSignatureMethod" && + change.IsBreakingChange); + + // Verify that property type changes are classified as breaking changes + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "ChangedTypeProperty" && + change.IsBreakingChange); + + // Verify that visibility increases are not classified as breaking changes + Assert.Contains(result.Modifications, change => + change.SourceMember.Name == "VisibilityChangedMethod" && + !change.IsBreakingChange); + } } } diff --git a/tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj b/tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj index 84fa524..1b23a43 100644 --- a/tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj +++ b/tests/DotNetApiDiff.Tests/DotNetApiDiff.Tests.csproj @@ -35,4 +35,10 @@ + + + + + + diff --git a/tests/DotNetApiDiff.Tests/Reporting/MarkdownFormatterTests.cs b/tests/DotNetApiDiff.Tests/Reporting/MarkdownFormatterTests.cs index c54b928..d901203 100644 --- a/tests/DotNetApiDiff.Tests/Reporting/MarkdownFormatterTests.cs +++ b/tests/DotNetApiDiff.Tests/Reporting/MarkdownFormatterTests.cs @@ -250,21 +250,24 @@ public void Format_OutputIsValidMarkdown() var markdown = formatter.Format(result); // Assert + // Normalize line endings for cross-platform compatibility + var normalizedMarkdown = markdown.Replace("\r\n", "\n").Replace("\r", "\n"); + // Check for valid markdown structure // Headers should start with # - Assert.Matches(new Regex(@"^# .*$", RegexOptions.Multiline), markdown); - Assert.Matches(new Regex(@"^## .*$", RegexOptions.Multiline), markdown); + Assert.Matches(new Regex(@"^# .*$", RegexOptions.Multiline), normalizedMarkdown); + Assert.Matches(new Regex(@"^## .*$", RegexOptions.Multiline), normalizedMarkdown); // Tables should have header row and separator row - Assert.Matches(new Regex(@"^\| .* \|$", RegexOptions.Multiline), markdown); - Assert.Matches(new Regex(@"^\|\-+\|\-+\|", RegexOptions.Multiline), markdown); + Assert.Matches(new Regex(@"^\| .* \|$", RegexOptions.Multiline), normalizedMarkdown); + Assert.Matches(new Regex(@"^\|\-+\|\-+\|", RegexOptions.Multiline), normalizedMarkdown); // Code blocks should be properly formatted - Assert.Matches(new Regex(@"```csharp\s.*\s```", RegexOptions.Singleline), markdown); + Assert.Matches(new Regex(@"```csharp\s.*\s```", RegexOptions.Singleline), normalizedMarkdown); // Details tags should be properly closed - var detailsOpenCount = Regex.Matches(markdown, @"
").Count; - var detailsCloseCount = Regex.Matches(markdown, @"
").Count; + var detailsOpenCount = Regex.Matches(normalizedMarkdown, @"
").Count; + var detailsCloseCount = Regex.Matches(normalizedMarkdown, @"
").Count; Assert.Equal(detailsOpenCount, detailsCloseCount); } } diff --git a/tests/DotNetApiDiff.Tests/TestData/README.md b/tests/DotNetApiDiff.Tests/TestData/README.md new file mode 100644 index 0000000..16877ff --- /dev/null +++ b/tests/DotNetApiDiff.Tests/TestData/README.md @@ -0,0 +1,79 @@ +# Test Assemblies for Integration Testing + +This directory contains test assemblies used for integration testing of the DotNetApiDiff tool. + +## Test Assembly Structure + +### TestAssemblyV1.dll + +Contains the following API elements: + +- `TestAssembly.PublicClass` + - `UnchangedMethod()` - Remains unchanged in V2 + - `RemovedMethod()` - Removed in V2 + - `ChangedSignatureMethod(string)` - Signature changed in V2 + - `UnchangedProperty` - Remains unchanged in V2 + - `RemovedProperty` - Removed in V2 + - `ChangedTypeProperty` (string) - Type changed in V2 + - `UnchangedField` - Remains unchanged in V2 + - `RemovedField` - Removed in V2 + - `UnchangedEvent` - Remains unchanged in V2 + - `RemovedEvent` - Removed in V2 + - `VisibilityChangedMethod()` (protected) - Visibility changed to public in V2 + +- `TestAssembly.IPublicInterface` + - `UnchangedMethod()` - Remains unchanged in V2 + - `RemovedMethod()` - Removed in V2 + - `UnchangedProperty` - Remains unchanged in V2 + - `RemovedProperty` - Removed in V2 + - `UnchangedEvent` - Remains unchanged in V2 + - `RemovedEvent` - Removed in V2 + +- `TestAssembly.NamespaceToBeRenamed.ClassInRenamedNamespace` + - `Method()` - Remains unchanged in V2 (but in renamed namespace) + - `Property` - Remains unchanged in V2 (but in renamed namespace) + +### TestAssemblyV2.dll + +Contains the following API elements: + +- `TestAssembly.PublicClass` + - `UnchangedMethod()` - Unchanged from V1 + - `ChangedSignatureMethod(int)` - Signature changed from V1 + - `NewMethod()` - Added in V2 + - `UnchangedProperty` - Unchanged from V1 + - `ChangedTypeProperty` (int) - Type changed from V1 + - `NewProperty` - Added in V2 + - `UnchangedField` - Unchanged from V1 + - `NewField` - Added in V2 + - `UnchangedEvent` - Unchanged from V1 + - `NewEvent` - Added in V2 + - `VisibilityChangedMethod()` (public) - Visibility changed from protected in V1 + +- `TestAssembly.IPublicInterface` + - `UnchangedMethod()` - Unchanged from V1 + - `NewMethod()` - Added in V2 + - `UnchangedProperty` - Unchanged from V1 + - `NewProperty` - Added in V2 + - `UnchangedEvent` - Unchanged from V1 + - `NewEvent` - Added in V2 + +- `TestAssembly.RenamedNamespace.ClassInRenamedNamespace` + - `Method()` - Unchanged from V1 (but in renamed namespace) + - `Property` - Unchanged from V1 (but in renamed namespace) + +## Building the Test Assemblies + +The test assemblies need to be built separately and placed in this directory for the integration tests to work. + +```bash +# Build TestAssemblyV1 +dotnet build TestAssemblyV1.csproj -c Release + +# Build TestAssemblyV2 +dotnet build TestAssemblyV2.csproj -c Release + +# Copy the assemblies to the TestData directory +cp bin/Release/net8.0/TestAssemblyV1.dll ../DotNetApiDiff.Tests/TestData/ +cp bin/Release/net8.0/TestAssemblyV2.dll ../DotNetApiDiff.Tests/TestData/ +``` diff --git a/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV1.dll b/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV1.dll new file mode 100644 index 0000000..8e8ef16 Binary files /dev/null and b/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV1.dll differ diff --git a/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV2.dll b/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV2.dll new file mode 100644 index 0000000..88b473a Binary files /dev/null and b/tests/DotNetApiDiff.Tests/TestData/TestAssemblyV2.dll differ diff --git a/tests/DotNetApiDiff.Tests/TestData/sample-config.json b/tests/DotNetApiDiff.Tests/TestData/sample-config.json index 5129716..4ddf2fa 100644 --- a/tests/DotNetApiDiff.Tests/TestData/sample-config.json +++ b/tests/DotNetApiDiff.Tests/TestData/sample-config.json @@ -1,24 +1,29 @@ { + "filters": { + "includeNamespaces": ["System.Text", "System.IO"], + "excludeNamespaces": ["System.Diagnostics", "System.Internal"], + "includeTypes": ["System.Text.*", "System.IO.*"], + "excludeTypes": ["*.Internal*", "*.Helper*"], + "includeInternals": false, + "includeCompilerGenerated": false + }, "mappings": { "namespaceMappings": { "OldNamespace": ["NewNamespace"], - "Legacy.Api": ["Modern.Api", "Modern.Api.V2"] + "AnotherOldNamespace": ["AnotherNewNamespace"] }, "typeMappings": { - "OldNamespace.OldType": "NewNamespace.NewType", - "Legacy.Api.LegacyClass": "Modern.Api.ModernClass" + "OldType": "NewType", + "AnotherOldType": "AnotherNewType" }, "autoMapSameNameTypes": true, "ignoreCase": true }, "exclusions": { - "excludedTypes": ["System.Diagnostics.Debug", "System.Diagnostics.Trace"], - "excludedTypePatterns": ["*.Internal.*", "*.Private.*"], - "excludedMembers": [ - "System.Object.Finalize", - "System.Object.MemberwiseClone" - ], - "excludedMemberPatterns": ["*.Obsolete*", "*.Debug*"] + "excludedTypes": ["ExcludedType", "AnotherExcludedType"], + "excludedMembers": ["SomeClass.RemovedMethod", "SomeClass.RemovedProperty"], + "excludedTypePatterns": ["*.Internal*", "*.Helper*"], + "excludedMemberPatterns": ["*.get_*", "*.set_*"] }, "breakingChangeRules": { "treatTypeRemovalAsBreaking": true, @@ -26,19 +31,5 @@ "treatAddedTypeAsBreaking": false, "treatAddedMemberAsBreaking": false, "treatSignatureChangeAsBreaking": true - }, - "filters": { - "includeNamespaces": ["System.Text", "System.IO"], - "excludeNamespaces": [ - "System.Diagnostics", - "System.Runtime.CompilerServices" - ], - "includeTypes": ["System.Text.*", "System.IO.File*"], - "excludeTypes": ["*.Internal*", "*.Debug*"], - "includeInternals": false, - "includeCompilerGenerated": false - }, - "outputFormat": "Console", - "outputPath": null, - "failOnBreakingChanges": true + } } diff --git a/tests/TestAssemblies/TestAssemblyV1.csproj b/tests/TestAssemblies/TestAssemblyV1.csproj new file mode 100644 index 0000000..4c61f0d --- /dev/null +++ b/tests/TestAssemblies/TestAssemblyV1.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + TestAssemblyV1 + TestAssembly + $(MSBuildThisFileDirectory)bin\$(Configuration)\ + false + + CS0067 + + + + + + + diff --git a/tests/TestAssemblies/TestAssemblyV2.csproj b/tests/TestAssemblies/TestAssemblyV2.csproj new file mode 100644 index 0000000..3edc9b1 --- /dev/null +++ b/tests/TestAssemblies/TestAssemblyV2.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + TestAssemblyV2 + TestAssembly + $(MSBuildThisFileDirectory)bin\$(Configuration)\ + false + + CS0067 + + + + + + + diff --git a/tests/TestAssemblies/V1/NamespaceToBeRenamed.cs b/tests/TestAssemblies/V1/NamespaceToBeRenamed.cs new file mode 100644 index 0000000..1b9e98a --- /dev/null +++ b/tests/TestAssemblies/V1/NamespaceToBeRenamed.cs @@ -0,0 +1,20 @@ +namespace TestAssembly.NamespaceToBeRenamed +{ + /// + /// A class in a namespace that will be renamed in V2 + /// + public class ClassInRenamedNamespace + { + /// + /// A method that will remain unchanged + /// + public void Method() + { + } + + /// + /// A property that will remain unchanged + /// + public string Property { get; set; } = string.Empty; + } +} diff --git a/tests/TestAssemblies/V1/PublicClass.cs b/tests/TestAssemblies/V1/PublicClass.cs new file mode 100644 index 0000000..944f662 --- /dev/null +++ b/tests/TestAssemblies/V1/PublicClass.cs @@ -0,0 +1,72 @@ +namespace TestAssembly +{ + /// + /// A public class that will remain unchanged between versions + /// + public class PublicClass + { + /// + /// A public method that will remain unchanged + /// + public void UnchangedMethod() + { + } + + /// + /// A public method that will be removed in V2 + /// + public void RemovedMethod() + { + } + + /// + /// A public method that will have its signature changed in V2 + /// + /// A string parameter + public void ChangedSignatureMethod(string value) + { + } + + /// + /// A public property that will remain unchanged + /// + public string UnchangedProperty { get; set; } = string.Empty; + + /// + /// A public property that will be removed in V2 + /// + public int RemovedProperty { get; set; } + + /// + /// A public property that will have its type changed in V2 + /// + public string ChangedTypeProperty { get; set; } = string.Empty; + + /// + /// A public field that will remain unchanged + /// + public readonly int UnchangedField = 42; + + /// + /// A public field that will be removed in V2 + /// + public readonly bool RemovedField = true; + + /// + /// A public event that will remain unchanged + /// + public event EventHandler? UnchangedEvent; + + /// + /// A public event that will be removed in V2 + /// + public event EventHandler? RemovedEvent; + + /// + /// A protected method that will have its visibility changed to public in V2 + /// + protected void VisibilityChangedMethod() + { + } + } +} diff --git a/tests/TestAssemblies/V1/PublicInterface.cs b/tests/TestAssemblies/V1/PublicInterface.cs new file mode 100644 index 0000000..7f2fe75 --- /dev/null +++ b/tests/TestAssemblies/V1/PublicInterface.cs @@ -0,0 +1,38 @@ +namespace TestAssembly +{ + /// + /// A public interface that will have members added and removed in V2 + /// + public interface IPublicInterface + { + /// + /// A method that will remain unchanged + /// + void UnchangedMethod(); + + /// + /// A method that will be removed in V2 + /// + void RemovedMethod(); + + /// + /// A property that will remain unchanged + /// + string UnchangedProperty { get; set; } + + /// + /// A property that will be removed in V2 + /// + int RemovedProperty { get; } + + /// + /// An event that will remain unchanged + /// + event EventHandler UnchangedEvent; + + /// + /// An event that will be removed in V2 + /// + event EventHandler RemovedEvent; + } +} diff --git a/tests/TestAssemblies/V2/PublicClass.cs b/tests/TestAssemblies/V2/PublicClass.cs new file mode 100644 index 0000000..4231153 --- /dev/null +++ b/tests/TestAssemblies/V2/PublicClass.cs @@ -0,0 +1,80 @@ +namespace TestAssembly +{ + /// + /// A public class that remains unchanged between versions + /// + public class PublicClass + { + /// + /// A public method that remains unchanged + /// + public void UnchangedMethod() + { + } + + // RemovedMethod is intentionally removed + + /// + /// A public method with a changed signature (parameter type changed) + /// + /// An integer parameter (was string in V1) + public void ChangedSignatureMethod(int value) + { + } + + /// + /// A new method added in V2 + /// + public void NewMethod() + { + } + + /// + /// A public property that remains unchanged + /// + public string UnchangedProperty { get; set; } = string.Empty; + + // RemovedProperty is intentionally removed + + /// + /// A public property with changed type (was string in V1) + /// + public int ChangedTypeProperty { get; set; } + + /// + /// A new property added in V2 + /// + public DateTime NewProperty { get; set; } + + /// + /// A public field that remains unchanged + /// + public readonly int UnchangedField = 42; + + // RemovedField is intentionally removed + + /// + /// A new field added in V2 + /// + public readonly double NewField = 3.14; + + /// + /// A public event that remains unchanged + /// + public event EventHandler? UnchangedEvent; + + // RemovedEvent is intentionally removed + + /// + /// A new event added in V2 + /// + public event EventHandler? NewEvent; + + /// + /// A method that had its visibility changed from protected to public + /// + public void VisibilityChangedMethod() + { + } + } +} diff --git a/tests/TestAssemblies/V2/PublicInterface.cs b/tests/TestAssemblies/V2/PublicInterface.cs new file mode 100644 index 0000000..e740270 --- /dev/null +++ b/tests/TestAssemblies/V2/PublicInterface.cs @@ -0,0 +1,44 @@ +namespace TestAssembly +{ + /// + /// A public interface with members added and removed + /// + public interface IPublicInterface + { + /// + /// A method that remains unchanged + /// + void UnchangedMethod(); + + // RemovedMethod is intentionally removed + + /// + /// A new method added in V2 + /// + void NewMethod(); + + /// + /// A property that remains unchanged + /// + string UnchangedProperty { get; set; } + + // RemovedProperty is intentionally removed + + /// + /// A new property added in V2 + /// + DateTime NewProperty { get; } + + /// + /// An event that remains unchanged + /// + event EventHandler UnchangedEvent; + + // RemovedEvent is intentionally removed + + /// + /// A new event added in V2 + /// + event EventHandler NewEvent; + } +} diff --git a/tests/TestAssemblies/V2/RenamedNamespace.cs b/tests/TestAssemblies/V2/RenamedNamespace.cs new file mode 100644 index 0000000..215a054 --- /dev/null +++ b/tests/TestAssemblies/V2/RenamedNamespace.cs @@ -0,0 +1,20 @@ +namespace TestAssembly.RenamedNamespace +{ + /// + /// A class in a namespace that was renamed from NamespaceToBeRenamed in V1 + /// + public class ClassInRenamedNamespace + { + /// + /// A method that remains unchanged + /// + public void Method() + { + } + + /// + /// A property that remains unchanged + /// + public string Property { get; set; } = string.Empty; + } +} diff --git a/tests/TestAssemblies/build-test-assemblies.sh b/tests/TestAssemblies/build-test-assemblies.sh new file mode 100755 index 0000000..87bbe1d --- /dev/null +++ b/tests/TestAssemblies/build-test-assemblies.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Build the test assemblies +echo "Building TestAssemblyV1..." +dotnet build TestAssemblyV1.csproj -c Release + +echo "Building TestAssemblyV2..." +dotnet build TestAssemblyV2.csproj -c Release + +# Copy the assemblies to the TestData directory for integration tests +echo "Copying assemblies to TestData directory..." +cp bin/Release/net8.0/TestAssemblyV1.dll ../DotNetApiDiff.Tests/TestData/ +cp bin/Release/net8.0/TestAssemblyV2.dll ../DotNetApiDiff.Tests/TestData/ + +echo "Done building and copying test assemblies."