提交 04e4d15d 编写于 作者: T tmeschter

Warn if analyzers have dependencies with the same identity but different contents.

Consider what happens if you have two analyzers, A and B, that each depend on an assembly named C.

  Directory 1:
    A.dll
    C.dll

  Directory 2:
    B.dll
    C.dll

If both copies of C have the same identity (name, version, culture, public key token, etc.) then only one of them is actually going to be loaded into VS. If both copies are identical then it doesn't matter, but if their contents differ the analyzers may not work the way they are supposed to or may fail outright.

Here we attempt to let the user know that this might happen. Whenever an analyzer is added or removed we identify the transitive set of assemblies the analyzers' may load and identify assemblies that have the same identity. We then hash the file contents of these assemblies and compare. If they are different we surface a conflict in the Error List.

In solutions with many different analyzers or dependencies this is a potentially expensive operation. To limit the impact on the system we only allow one of these operations to run at a time. If an analyzer is added or removed while a check is in progress we immediately signal for cancellation, wait for the existing task to finish up, and only then start the new one. (changeset 1411056)
上级 9065cff1
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Security;
using System.Threading;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
internal sealed class AnalyzerDependencyChecker
{
private readonly ImmutableHashSet<string> _analyzerFilePaths;
private readonly SortedSet<string> _examinedFilePaths = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly SortedSet<string> _filePathsToExamine = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, string> _dependencyPathToAnalyzerPathMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public AnalyzerDependencyChecker(IEnumerable<string> analyzerFilePaths)
{
_analyzerFilePaths = analyzerFilePaths.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
}
public ImmutableArray<AnalyzerDependencyConflict> Run(CancellationToken cancellationToken = default(CancellationToken))
{
foreach (var analyzerFilePath in _analyzerFilePaths)
{
cancellationToken.ThrowIfCancellationRequested();
if (File.Exists(analyzerFilePath))
{
AddDependenciesToWorkList(analyzerFilePath, analyzerFilePath);
}
}
List<DependencyInfo> dependencies = new List<DependencyInfo>();
while (_filePathsToExamine.Count > 0)
{
cancellationToken.ThrowIfCancellationRequested();
string filePath = _filePathsToExamine.Min;
_filePathsToExamine.Remove(filePath);
_examinedFilePaths.Add(filePath);
AssemblyIdentity assemblyIdentity = TryReadAssemblyIdentity(filePath);
if (assemblyIdentity != null)
{
var analyzerPath = _dependencyPathToAnalyzerPathMap[filePath];
dependencies.Add(new DependencyInfo(filePath, assemblyIdentity, analyzerPath));
AddDependenciesToWorkList(analyzerPath, filePath);
}
}
ImmutableArray<AnalyzerDependencyConflict>.Builder conflicts = ImmutableArray.CreateBuilder<AnalyzerDependencyConflict>();
foreach (var identityGroup in dependencies.GroupBy(di => di.Identity))
{
var identityGroupArray = identityGroup.ToImmutableArray();
for (int i = 0; i < identityGroupArray.Length; i++)
{
for (int j = i + 1; j < identityGroupArray.Length; j++)
{
cancellationToken.ThrowIfCancellationRequested();
byte[] hash1;
byte[] hash2;
if ((hash1 = identityGroupArray[i].TryGetFileHash()) != null &&
(hash2 = identityGroupArray[j].TryGetFileHash()) != null &&
!HashesAreEqual(hash1, hash2))
{
conflicts.Add(new AnalyzerDependencyConflict(
identityGroupArray[i].DependencyFilePath,
identityGroupArray[j].DependencyFilePath,
identityGroupArray[i].AnalyzerFilePath,
identityGroupArray[j].AnalyzerFilePath));
}
}
}
}
return conflicts.ToImmutable();
}
private bool HashesAreEqual(byte[] hash1, byte[] hash2)
{
for (int i = 0; i < hash1.Length; i++)
{
if (hash1[i] != hash2[i])
{
return false;
}
}
return true;
}
private void AddDependenciesToWorkList(string analyzerFilePath, string assemblyPath)
{
ImmutableArray<string> referencedAssemblyNames = GetReferencedAssemblyNames(assemblyPath);
foreach (var reference in referencedAssemblyNames)
{
string referenceFilePath = Path.Combine(Path.GetDirectoryName(analyzerFilePath), reference + ".dll");
if (!_examinedFilePaths.Contains(referenceFilePath) &&
File.Exists(referenceFilePath))
{
_filePathsToExamine.Add(referenceFilePath);
_dependencyPathToAnalyzerPathMap[referenceFilePath] = analyzerFilePath;
}
}
}
private ImmutableArray<string> GetReferencedAssemblyNames(string assemblyPath)
{
try
{
using (var stream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
using (var peReader = new PEReader(stream))
{
var metadataReader = peReader.GetMetadataReader();
var builder = ImmutableArray.CreateBuilder<string>();
foreach (var referenceHandle in metadataReader.AssemblyReferences)
{
var reference = metadataReader.GetAssemblyReference(referenceHandle);
builder.Add(metadataReader.GetString(reference.Name));
}
return builder.ToImmutable();
}
}
catch { }
return ImmutableArray<string>.Empty;
}
private AssemblyIdentity TryReadAssemblyIdentity(string filePath)
{
try
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
using (var peReader = new PEReader(stream))
{
var metadataReader = peReader.GetMetadataReader();
string name;
Version version;
string cultureName;
ImmutableArray<byte> publicKeyToken;
var assemblyDefinition = metadataReader.GetAssemblyDefinition();
name = metadataReader.GetString(assemblyDefinition.Name);
version = assemblyDefinition.Version;
cultureName = metadataReader.GetString(assemblyDefinition.Culture);
publicKeyToken = metadataReader.GetBlobContent(assemblyDefinition.PublicKey);
return new AssemblyIdentity(name, version, cultureName, publicKeyToken, hasPublicKey: false);
}
}
catch { }
return null;
}
private sealed class DependencyInfo
{
private byte[] _lazyFileHash;
private bool _triedToComputeFileHash = false;
public DependencyInfo(string dependencyFilePath, AssemblyIdentity identity, string analyzerFilePath)
{
DependencyFilePath = dependencyFilePath;
Identity = identity;
AnalyzerFilePath = analyzerFilePath;
}
public string AnalyzerFilePath { get; }
public string DependencyFilePath { get; }
public AssemblyIdentity Identity { get; }
public byte[] TryGetFileHash()
{
if (!_triedToComputeFileHash)
{
_triedToComputeFileHash = true;
_lazyFileHash = TryComputeFileHash(DependencyFilePath);
}
return _lazyFileHash;
}
private byte[] TryComputeFileHash(string filePath)
{
try
{
using (var cryptoProvider = new SHA1CryptoServiceProvider())
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
{
return cryptoProvider.ComputeHash(stream);
}
}
catch { }
return null;
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
[Export(typeof(AnalyzerDependencyCheckingService))]
internal sealed class AnalyzerDependencyCheckingService
{
private static readonly object s_dependencyConflictErrorId = new object();
private readonly VisualStudioWorkspaceImpl _workspace;
private readonly HostDiagnosticUpdateSource _updateSource;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private Task<ImmutableArray<AnalyzerDependencyConflict>> _task = Task.FromResult(ImmutableArray<AnalyzerDependencyConflict>.Empty);
private ImmutableHashSet<string> _analyzerPaths = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase);
[ImportingConstructor]
public AnalyzerDependencyCheckingService(
VisualStudioWorkspaceImpl workspace,
HostDiagnosticUpdateSource updateSource)
{
_workspace = workspace;
_updateSource = updateSource;
}
public async void CheckForConflictsAsync()
{
try
{
ImmutableArray<AnalyzerDependencyConflict> conflicts = await GetConflictsAsync().ConfigureAwait(continueOnCapturedContext: true);
var builder = ImmutableArray.CreateBuilder<DiagnosticData>();
foreach (var project in _workspace.ProjectTracker.Projects)
{
builder.Clear();
foreach (var conflict in conflicts)
{
if (project.CurrentProjectAnalyzersContains(conflict.AnalyzerFilePath1) ||
project.CurrentProjectAnalyzersContains(conflict.AnalyzerFilePath2))
{
builder.Add(CreateDiagnostic(project.Id, conflict));
}
}
_updateSource.UpdateDiagnosticsForProject(project.Id, s_dependencyConflictErrorId, builder.ToImmutable());
}
foreach (var conflict in conflicts)
{
LogConflict(conflict);
}
}
catch (OperationCanceledException) { }
}
private void LogConflict(AnalyzerDependencyConflict conflict)
{
Logger.Log(
FunctionId.AnalyzerDependencyCheckingService_CheckForConflictsAsync,
KeyValueLogMessage.Create(m =>
{
m["Dependency1"] = Path.GetFileName(conflict.DependencyFilePath1);
m["Dependency2"] = Path.GetFileName(conflict.DependencyFilePath2);
m["Analyzer1"] = Path.GetFileName(conflict.AnalyzerFilePath1);
m["Analyzer2"] = Path.GetFileName(conflict.AnalyzerFilePath2);
}));
}
private DiagnosticData CreateDiagnostic(ProjectId projectId, AnalyzerDependencyConflict conflict)
{
string id = ServicesVSResources.WRN_AnalyzerDependencyConflictId;
string category = ServicesVSResources.ErrorCategory;
string message = string.Format(
ServicesVSResources.WRN_AnalyzerDependencyConflictMessage,
conflict.DependencyFilePath1,
Path.GetFileNameWithoutExtension(conflict.AnalyzerFilePath1),
conflict.DependencyFilePath2,
Path.GetFileNameWithoutExtension(conflict.AnalyzerFilePath2));
DiagnosticData data = new DiagnosticData(
id,
category,
message,
ServicesVSResources.WRN_AnalyzerDependencyConflictMessage,
severity: DiagnosticSeverity.Warning,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
warningLevel: 0,
customTags: ImmutableArray<string>.Empty,
workspace: _workspace,
projectId: projectId);
return data;
}
private Task<ImmutableArray<AnalyzerDependencyConflict>> GetConflictsAsync()
{
ImmutableHashSet<string> currentAnalyzerPaths = _workspace.CurrentSolution
.Projects
.SelectMany(p => p.AnalyzerReferences)
.OfType<AnalyzerFileReference>()
.Select(a => a.FullPath)
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
if (currentAnalyzerPaths.SetEquals(_analyzerPaths))
{
return _task;
}
_cancellationTokenSource.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
_analyzerPaths = currentAnalyzerPaths;
_task = _task.SafeContinueWith(_ =>
{
return new AnalyzerDependencyChecker(currentAnalyzerPaths).Run(_cancellationTokenSource.Token);
},
TaskScheduler.Default);
return _task;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
internal sealed class AnalyzerDependencyConflict
{
public AnalyzerDependencyConflict(string dependencyFilePath1, string dependencyFilePath2, string analyzerFilePath1, string analyzerFilePath2)
{
DependencyFilePath1 = dependencyFilePath1;
DependencyFilePath2 = dependencyFilePath2;
AnalyzerFilePath1 = analyzerFilePath1;
AnalyzerFilePath2 = analyzerFilePath2;
}
public string DependencyFilePath1 { get; }
public string DependencyFilePath2 { get; }
public string AnalyzerFilePath1 { get; }
public string AnalyzerFilePath2 { get; }
}
}
......@@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
internal partial class AbstractProject : IAnalyzerHost
{
private AnalyzerFileWatcherService _analyzerFileWatcherService = null;
private AnalyzerDependencyCheckingService _dependencyCheckingService = null;
public void AddAnalyzerAssembly(string analyzerAssemblyFullPath)
{
......@@ -30,6 +31,8 @@ public void AddAnalyzerAssembly(string analyzerAssemblyFullPath)
{
var analyzerReference = analyzer.GetReference();
this.ProjectTracker.NotifyWorkspaceHosts(host => host.OnAnalyzerReferenceAdded(_id, analyzerReference));
GetAnalyzerDependencyCheckingService().CheckForConflictsAsync();
}
GetAnalyzerFileWatcherService().ErrorIfAnalyzerAlreadyLoaded(_id, analyzerAssemblyFullPath);
......@@ -51,6 +54,8 @@ public void RemoveAnalyzerAssembly(string analyzerAssemblyFullPath)
{
var analyzerReference = analyzer.GetReference();
this.ProjectTracker.NotifyWorkspaceHosts(host => host.OnAnalyzerReferenceRemoved(_id, analyzerReference));
GetAnalyzerDependencyCheckingService().CheckForConflictsAsync();
}
analyzer.Dispose();
......@@ -146,5 +151,17 @@ private AnalyzerFileWatcherService GetAnalyzerFileWatcherService()
return _analyzerFileWatcherService;
}
private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService()
{
if (_dependencyCheckingService == null)
{
var componentModel = (IComponentModel)this.ServiceProvider.GetService(typeof(SComponentModel));
_dependencyCheckingService = componentModel.GetService<AnalyzerDependencyCheckingService>();
}
return _dependencyCheckingService;
}
}
}
\ No newline at end of file
}
......@@ -993,6 +993,24 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to AnalyzerDependencyConflict.
/// </summary>
internal static string WRN_AnalyzerDependencyConflictId {
get {
return ResourceManager.GetString("WRN_AnalyzerDependencyConflictId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Assembly &apos;{0}&apos; used by analyzer &apos;{1}&apos; and assembly &apos;{2}&apos; used by analyzer &apos;{3}&apos; have the same identity but different contents. These analyzers may not run correctly..
/// </summary>
internal static string WRN_AnalyzerDependencyConflictMessage {
get {
return ResourceManager.GetString("WRN_AnalyzerDependencyConflictMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The assembly {0} does not contain any analyzers..
/// </summary>
......
......@@ -441,4 +441,10 @@ Use the dropdown to view and switch to other projects this file may belong to.</
<data name="PreviewChangesProjectReference" xml:space="preserve">
<value>Project reference to '{0}' in project '{1}'</value>
</data>
<data name="WRN_AnalyzerDependencyConflictId" xml:space="preserve">
<value>AnalyzerDependencyConflict</value>
</data>
<data name="WRN_AnalyzerDependencyConflictMessage" xml:space="preserve">
<value>Assembly '{0}' used by analyzer '{1}' and assembly '{2}' used by analyzer '{3}' have the same identity but different contents. These analyzers may not run correctly.</value>
</data>
</root>
\ No newline at end of file
......@@ -18,6 +18,9 @@
</PropertyGroup>
<ItemGroup Label="Build Items">
<Compile Include="..\..\..\Compilers\Core\Desktop\IVsSQM.cs" />
<Compile Include="Implementation\AnalyzerDependencyChecker.cs" />
<Compile Include="Implementation\AnalyzerDependencyCheckingService.cs" />
<Compile Include="Implementation\AnalyzerDependencyConflict.cs" />
<Compile Include="Implementation\CompilationErrorTelemetry\CompilationErrorTelemetryIncrementalAnalyzer.cs" />
<Compile Include="Implementation\Diagnostics\VisualStudioVenusSpanMappingService.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.MetadataReferenceChange.cs" />
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.IO
Imports System.Text
Imports System.Threading
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.VisualStudio.LanguageServices.Implementation
Imports Roslyn.Test.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
Public Class AnalyzerDependencyCheckerTests
Inherits TestBase
Shared CSharpCompilerExecutable As String = Path.Combine(Path.GetDirectoryName(GetType(AnalyzerDependencyCheckerTests).Assembly.Location), "csc.exe")
<Fact, WorkItem(1064914)>
Public Sub Test1()
' Dependency Graph:
' A
Using directory = New DisposableDirectory(Temp)
Dim library = BuildLibrary(directory, "public class A { }", "A")
Dim dependencyChecker = New AnalyzerDependencyChecker({library})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test2()
' Dependency graph:
' A --> B
Dim sourceA = "
public class A
{
void M()
{
B b = new B();
}
}"
Dim sourceB = "public class B { }"
Using directory = New DisposableDirectory(Temp)
Dim libraryB = BuildLibrary(directory, sourceB, "B")
Dim libraryA = BuildLibrary(directory, sourceA, "A", "B")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test3()
' Dependency graph:
' A --> B
' \
' -> C
Dim sourceA = "
public class A
{
void M()
{
B b = new B();
C c = new C();
}
}"
Dim sourceB = "public class B { }"
Dim sourceC = "public class C { }"
Using directory = New DisposableDirectory(Temp)
Dim libraryC = BuildLibrary(directory, sourceC, "C")
Dim libraryB = BuildLibrary(directory, sourceB, "B")
Dim libraryA = BuildLibrary(directory, sourceA, "A", "B", "C")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test4()
' Dependency graph:
' A --> B
' C --> D
Dim sourceA = "
public class A
{
void M()
{
B b = new B();
}
}"
Dim sourceB = "public class B { }"
Dim sourceC = "
public class C
{
void M()
{
C c = new C();
}
}"
Dim sourceD = "public class D { }"
Using directory = New DisposableDirectory(Temp)
Dim libraryB = BuildLibrary(directory, sourceB, "B")
Dim libraryA = BuildLibrary(directory, sourceA, "A", "B")
Dim libraryD = BuildLibrary(directory, sourceD, "D")
Dim libraryC = BuildLibrary(directory, sourceC, "C", "D")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryC})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test5()
' Dependency graph:
' Directory 1:
' A --> B
' Directory 2:
' C --> D
Dim sourceA = "
public class A
{
void M()
{
B b = new B();
}
}"
Dim sourceB = "public class B { }"
Dim sourceC = "
public class C
{
void M()
{
C c = new C();
}
}"
Dim sourceD = "public class D { }"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryB = BuildLibrary(directory1, sourceB, "B")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "B")
Dim libraryD = BuildLibrary(directory2, sourceD, "D")
Dim libraryC = BuildLibrary(directory2, sourceC, "C", "D")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryC})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test6()
' Dependency graph:
' A -
' \
' -> C
' /
' B -
Dim sourceA = "
public class A
{
void M()
{
C c = new C();
}
}"
Dim sourceB = "
public class B
{
void M()
{
C c = new C();
}
}"
Dim sourceC = "public class C { }"
Using directory = New DisposableDirectory(Temp)
Dim libraryC = BuildLibrary(directory, sourceC, "C")
Dim libraryA = BuildLibrary(directory, sourceA, "A", "C")
Dim libraryB = BuildLibrary(directory, sourceB, "B", "C")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test7()
' Dependency graph:
' Directory 1:
' A --> C
' Directory 2:
' B --> C
Dim sourceA = "
public class A
{
void M()
{
C c = new C();
}
}"
Dim sourceB = "
public class B
{
void M()
{
C c = new C();
}
}"
Dim sourceC = "public class C { }"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryC1 = BuildLibrary(directory1, sourceC, "C")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "C")
Dim libraryC2 = directory2.CreateFile("C.dll").CopyContentFrom(libraryC1)
Dim libraryB = BuildLibrary(directory2, sourceB, "B", "C")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB})
Dim results = dependencyChecker.Run()
Assert.Empty(results)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test8()
' Dependency graph:
' Directory 1:
' A --> C
' Directory 2:
' B --> C'
Dim sourceA = "
public class A
{
void M()
{
C c = new C();
}
}"
Dim sourceB = "
public class B
{
void M()
{
C c = new C();
}
}"
Dim sourceC = "
public class C
{
public static string Field = ""Assembly C"";
}"
Dim sourceCPrime = "
public class C
{
public static string Field = ""Assembly C Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryC = BuildLibrary(directory1, sourceC, "C")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "C")
Dim libraryCPrime = BuildLibrary(directory2, sourceCPrime, "C")
Dim libraryB = BuildLibrary(directory2, sourceB, "B", "C")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=1, actual:=results.Length)
Dim analyzer1FileName As String = Path.GetFileName(results(0).AnalyzerFilePath1)
Dim analyzer2FileName As String = Path.GetFileName(results(0).AnalyzerFilePath2)
Dim dependency1FileName As String = Path.GetFileName(results(0).DependencyFilePath1)
Dim dependency2FileName As String = Path.GetFileName(results(0).DependencyFilePath2)
Assert.True((analyzer1FileName = "A.dll" AndAlso analyzer2FileName = "B.dll") OrElse
(analyzer1FileName = "B.dll" AndAlso analyzer2FileName = "A.dll"))
Assert.Equal(expected:="C.dll", actual:=dependency1FileName)
Assert.Equal(expected:="C.dll", actual:=dependency2FileName)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test9()
' Dependency graph:
' Directory 1:
' A --> C --> D
' Directory 2:
' B --> C --> D'
Dim sourceA = "
public class A
{
void M()
{
C c = new C();
}
}"
Dim sourceB = "
public class B
{
void M()
{
C c = new C();
}
}"
Dim sourceC = "
public class C
{
void M()
{
D d = new D();
}
}"
Dim sourceD = "
public class D
{
public static string Field = ""Assembly D"";
}"
Dim sourceDPrime = "
public class D
{
public static string Field = ""Assembly D Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryD = BuildLibrary(directory1, sourceD, "D")
Dim libraryDPrime = BuildLibrary(directory2, sourceDPrime, "D")
Dim libraryC1 = BuildLibrary(directory1, sourceC, "C", "D")
Dim libraryC2 = directory2.CreateFile("C.dll").CopyContentFrom(libraryC1)
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "C")
Dim libraryB = BuildLibrary(directory2, sourceB, "B", "C")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=1, actual:=results.Length)
Dim analyzer1FileName As String = Path.GetFileName(results(0).AnalyzerFilePath1)
Dim analyzer2FileName As String = Path.GetFileName(results(0).AnalyzerFilePath2)
Dim dependency1FileName As String = Path.GetFileName(results(0).DependencyFilePath1)
Dim dependency2FileName As String = Path.GetFileName(results(0).DependencyFilePath2)
Assert.True((analyzer1FileName = "A.dll" AndAlso analyzer2FileName = "B.dll") OrElse
(analyzer1FileName = "B.dll" AndAlso analyzer2FileName = "A.dll"))
Assert.Equal(expected:="D.dll", actual:=dependency1FileName)
Assert.Equal(expected:="D.dll", actual:=dependency2FileName)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test10()
' Dependency graph:
' Directory 1:
' A --> C --> E
' Directory 2:
' B --> D --> E'
Dim sourceA = "
public class A
{
void M()
{
C c = new C();
}
}"
Dim sourceB = "
public class B
{
void M()
{
D d = new D();
}
}"
Dim sourceC = "
public class C
{
void M()
{
E e = new E();
}
}"
Dim sourceD = "
public class D
{
void M()
{
E e = new E();
}
}"
Dim sourceE = "
public class E
{
public static string Field = ""Assembly E"";
}"
Dim sourceEPrime = "
public class E
{
public static string Field = ""Assembly D Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryE = BuildLibrary(directory1, sourceE, "E")
Dim libraryEPrime = BuildLibrary(directory2, sourceEPrime, "E")
Dim libraryC = BuildLibrary(directory1, sourceC, "C", "E")
Dim libraryD = BuildLibrary(directory2, sourceD, "D", "E")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "C")
Dim libraryB = BuildLibrary(directory2, sourceB, "B", "D")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=1, actual:=results.Length)
Dim analyzer1FileName As String = Path.GetFileName(results(0).AnalyzerFilePath1)
Dim analyzer2FileName As String = Path.GetFileName(results(0).AnalyzerFilePath2)
Dim dependency1FileName As String = Path.GetFileName(results(0).DependencyFilePath1)
Dim dependency2FileName As String = Path.GetFileName(results(0).DependencyFilePath2)
Assert.True((analyzer1FileName = "A.dll" AndAlso analyzer2FileName = "B.dll") OrElse
(analyzer1FileName = "B.dll" AndAlso analyzer2FileName = "A.dll"))
Assert.Equal(expected:="E.dll", actual:=dependency1FileName)
Assert.Equal(expected:="E.dll", actual:=dependency2FileName)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test11()
' Dependency graph:
' Directory 1:
' A --> B
' Directory 2:
' A --> B'
Dim sourceA = "
public class A
{
void M()
{
B b = new B();
}
}"
Dim sourceB = "
public class B
{
public static string Field = ""Assembly B"";
}"
Dim sourceBPrime = "
public class B
{
public static string Field = ""Assembly B Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryB = BuildLibrary(directory1, sourceB, "B")
Dim libraryA1 = BuildLibrary(directory1, sourceA, "A", "B")
Dim libraryBPrime = BuildLibrary(directory2, sourceBPrime, "B")
Dim libraryA2 = directory2.CreateFile("A.dll").CopyContentFrom(libraryA1).Path
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA1, libraryA2})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=1, actual:=results.Length)
Dim analyzer1FileName As String = Path.GetFileName(results(0).AnalyzerFilePath1)
Dim analyzer2FileName As String = Path.GetFileName(results(0).AnalyzerFilePath2)
Dim dependency1FileName As String = Path.GetFileName(results(0).DependencyFilePath1)
Dim dependency2FileName As String = Path.GetFileName(results(0).DependencyFilePath2)
Assert.Equal(expected:="A.dll", actual:=analyzer1FileName)
Assert.Equal(expected:="A.dll", actual:=analyzer2FileName)
Assert.Equal(expected:="B.dll", actual:=dependency1FileName)
Assert.Equal(expected:="B.dll", actual:=dependency2FileName)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test12()
' Dependency graph:
' Directory 1:
' A --> B
' Directory 2:
' A' --> B'
Dim sourceA = "
public class A
{
public static string Field = ""Assembly A"";
void M()
{
B b = new B();
}
}"
Dim sourceAPrime = "
public class A
{
public static string Field = ""Assembly A Prime"";
void M()
{
B b = new B();
}
}"
Dim sourceB = "
public class B
{
public static string Field = ""Assembly B"";
}"
Dim sourceBPrime = "
public class B
{
public static string Field = ""Assembly B Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryB = BuildLibrary(directory1, sourceB, "B")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "B")
Dim libraryBPrime = BuildLibrary(directory2, sourceBPrime, "B")
Dim libraryAPrime = BuildLibrary(directory2, sourceAPrime, "A", "B")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryAPrime})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=1, actual:=results.Length)
Dim analyzer1FileName As String = Path.GetFileName(results(0).AnalyzerFilePath1)
Dim analyzer2FileName As String = Path.GetFileName(results(0).AnalyzerFilePath2)
Dim dependency1FileName As String = Path.GetFileName(results(0).DependencyFilePath1)
Dim dependency2FileName As String = Path.GetFileName(results(0).DependencyFilePath2)
Assert.Equal(expected:="A.dll", actual:=analyzer1FileName)
Assert.Equal(expected:="A.dll", actual:=analyzer2FileName)
Assert.Equal(expected:="B.dll", actual:=dependency1FileName)
Assert.Equal(expected:="B.dll", actual:=dependency2FileName)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test13()
' Dependency graph:
' Directory 1:
' A --> B
' Directory 2:
' A' --> B
Dim sourceA = "
public class A
{
public static string Field = ""Assembly A"";
void M()
{
B b = new B();
}
}"
Dim sourceAPrime = "
public class A
{
public static string Field = ""Assembly A Prime"";
void M()
{
B b = new B();
}
}"
Dim sourceB = "
public class B
{
public static string Field = ""Assembly B"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp)
Dim libraryB1 = BuildLibrary(directory1, sourceB, "B")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "B")
Dim libraryB2 = directory2.CreateFile("B.dll").CopyContentFrom(libraryB1).Path
Dim libraryAPrime = BuildLibrary(directory2, sourceAPrime, "A", "B")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryAPrime})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=0, actual:=results.Length)
End Using
End Sub
<Fact, WorkItem(1064914)>
Public Sub Test14()
' Dependency graph:
' Directory 1:
' A --> D
' Directory 2:
' B --> D'
' Directory 3:
' C --> D''
Dim sourceA = "
public class A
{
void M()
{
D d = new D();
}
}"
Dim sourceB = "
public class B
{
void M()
{
D d = new D();
}
}"
Dim sourceC = "
public class C
{
void M()
{
D d = new D();
}
}"
Dim sourceD = "
public class D
{
public static string Field = ""Assembly D"";
}"
Dim sourceDPrime = "
public class D
{
public static string Field = ""Assembly D Prime"";
}"
Dim sourceDPrimePrime = "
public class D
{
public static string Field = ""Assembly D Prime Prime"";
}"
Using directory1 = New DisposableDirectory(Temp), directory2 = New DisposableDirectory(Temp), directory3 = New DisposableDirectory(Temp)
Dim libraryD = BuildLibrary(directory1, sourceD, "D")
Dim libraryA = BuildLibrary(directory1, sourceA, "A", "D")
Dim libraryDPrime = BuildLibrary(directory2, sourceDPrime, "D")
Dim libraryB = BuildLibrary(directory2, sourceB, "B", "D")
Dim libraryDPrimePrime = BuildLibrary(directory3, sourceDPrimePrime, "D")
Dim libraryC = BuildLibrary(directory3, sourceC, "C", "D")
Dim dependencyChecker = New AnalyzerDependencyChecker({libraryA, libraryB, libraryC})
Dim results = dependencyChecker.Run()
Assert.Equal(expected:=3, actual:=results.Length)
End Using
End Sub
Private Function BuildLibrary(directory As DisposableDirectory, fileContents As String, libraryName As String, ParamArray referenceNames As String()) As String
Dim sourceFile = directory.CreateFile(libraryName + ".cs").WriteAllText(fileContents).Path
Dim tempOut = Path.Combine(directory.Path, libraryName + ".out")
Dim libraryOut = Path.Combine(directory.Path, libraryName + ".dll")
Dim sb = New StringBuilder
For Each name In referenceNames
sb.Append(" /r:")
sb.Append(Path.Combine(directory.Path, name + ".dll"))
Next
Dim references = sb.ToString()
Dim arguments = $"/C ""{CSharpCompilerExecutable}"" /nologo /t:library /out:{libraryOut} {references} {sourceFile} > {tempOut}"
Dim output = RunAndGetOutput("cmd", arguments, expectedRetCode:=0)
Return libraryOut
End Function
End Class
End Namespace
......@@ -198,6 +198,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AbstractTextViewFilterTests.vb" />
<Compile Include="AnalyzerSupport\AnalyzerDependencyCheckerTests.vb" />
<Compile Include="ChangeSignature\ChangeSignatureViewModelTests.vb" />
<Compile Include="ChangeSignature\ChangeSignatureViewModelTestState.vb" />
<Compile Include="CodeModel\AbstractCodeAttributeTests.vb" />
......
......@@ -297,5 +297,6 @@ internal enum FunctionId
Tagger_Diagnostics_Updated,
SuggestedActions_HasSuggestedActionsAsync,
SuggestedActions_GetSuggestedActions,
AnalyzerDependencyCheckingService_CheckForConflictsAsync
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册