Skip to content

Commit

Permalink
Initial support for project references
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed May 28, 2019
1 parent 5585346 commit aa93894
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public virtual async Task RunAsync(CancellationToken cancellationToken = default
var fixableDiagnostics = ImmutableArray<string>.Empty;
var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath);

await VerifyDiagnosticsAsync(testState.Sources.ToArray(), testState.AdditionalFiles.ToArray(), testState.AdditionalReferences.ToArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false);
await VerifyDiagnosticsAsync(testState.Sources.ToArray(), testState.AdditionalFiles.ToArray(), testState.AdditionalProjects.ToArray(), testState.AdditionalReferences.ToArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -182,21 +182,22 @@ protected internal virtual DiagnosticDescriptor GetDefaultDiagnostic(DiagnosticA
/// </summary>
/// <param name="sources">An array of strings to create source documents from to run the analyzers on.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="expected">A collection of <see cref="DiagnosticResult"/>s that should appear after the analyzer
/// is run on the sources.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected async Task VerifyDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
protected async Task VerifyDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
var analyzers = GetDiagnosticAnalyzers().ToImmutableArray();
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources, additionalFiles, additionalMetadataReferences, analyzers, verifier, cancellationToken).ConfigureAwait(false), analyzers, expected, verifier);
await VerifyGeneratedCodeDiagnosticsAsync(analyzers, sources, additionalFiles, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
await VerifySuppressionDiagnosticsAsync(analyzers, sources, additionalFiles, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources, additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, verifier, cancellationToken).ConfigureAwait(false), analyzers, expected, verifier);
await VerifyGeneratedCodeDiagnosticsAsync(analyzers, sources, additionalFiles, additionalProjects, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
await VerifySuppressionDiagnosticsAsync(analyzers, sources, additionalFiles, additionalProjects, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
}

private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
if (TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedCodeCheck))
{
Expand All @@ -218,10 +219,10 @@ private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<Diagnostic

var generatedCodeVerifier = verifier.PushContext("Verifying exclusions in <auto-generated> code");
var commentPrefix = Language == LanguageNames.CSharp ? "//" : "'";
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $" {commentPrefix} <auto-generated>\r\n"))).ToArray(), additionalFiles, additionalMetadataReferences, analyzers, generatedCodeVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, generatedCodeVerifier);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $" {commentPrefix} <auto-generated>\r\n"))).ToArray(), additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, generatedCodeVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, generatedCodeVerifier);
}

private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
if (TestBehaviors.HasFlag(TestBehaviors.SkipSuppressionCheck))
{
Expand All @@ -245,7 +246,7 @@ private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAn
var suppressionVerifier = verifier.PushContext($"Verifying exclusions in '{prefix}' code");
var suppressedDiagnostics = expected.Where(x => IsSubjectToExclusion(x, sources)).Select(x => x.Id).Distinct();
var suppression = prefix + " " + string.Join(", ", suppressedDiagnostics);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToArray(), additionalFiles, additionalMetadataReferences, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToArray(), additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier);
}

/// <summary>
Expand Down Expand Up @@ -476,15 +477,16 @@ private static bool IsInSourceFile(DiagnosticResult result, (string filename, So
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="analyzers">The analyzers to be run on the sources.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A collection of <see cref="Diagnostic"/>s that surfaced in the source code, sorted by
/// <see cref="Diagnostic.Location"/>.</returns>
private Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, ImmutableArray<DiagnosticAnalyzer> analyzers, IVerifier verifier, CancellationToken cancellationToken)
private Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, ImmutableArray<DiagnosticAnalyzer> analyzers, IVerifier verifier, CancellationToken cancellationToken)
{
return GetSortedDiagnosticsAsync(GetSolution(sources, additionalFiles, additionalMetadataReferences, verifier), analyzers, CompilerDiagnostics, cancellationToken);
return GetSortedDiagnosticsAsync(GetSolution(sources, additionalFiles, additionalProjects, additionalMetadataReferences, verifier), analyzers, CompilerDiagnostics, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -553,14 +555,15 @@ protected virtual AnalyzerOptions GetAnalyzerOptions(Project project)
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <returns>A solution containing a project with the specified sources and additional files.</returns>
private Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IVerifier verifier)
private Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, IVerifier verifier)
{
verifier.LanguageIsSupported(Language);

var project = CreateProject(sources, additionalFiles, additionalMetadataReferences, Language);
var project = CreateProject(sources, additionalFiles, additionalProjects, additionalMetadataReferences, Language);
var documents = project.Documents.ToArray();

verifier.Equal(sources.Length, documents.Length, "Amount of sources did not match amount of Documents created");
Expand All @@ -577,15 +580,16 @@ private Solution GetSolution((string filename, SourceText content)[] sources, (s
/// </remarks>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language)
protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
{
language = language ?? language;
var project = CreateProjectImpl(sources, additionalFiles, additionalMetadataReferences, language);
var project = CreateProjectImpl(sources, additionalFiles, additionalProjects, additionalMetadataReferences, language);
return ApplyCompilationOptions(project);
}

Expand All @@ -594,19 +598,35 @@ protected Project CreateProject((string filename, SourceText content)[] sources,
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language)
protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
{
var fileNamePrefix = DefaultFilePathPrefix;
var fileExt = DefaultFileExt;

var projectId = ProjectId.CreateNewId(debugName: DefaultTestProjectName);
var solution = CreateSolution(projectId, language);

foreach (var projectState in additionalProjects)
{
var additionalProjectId = ProjectId.CreateNewId(debugName: projectState.Name);
solution = solution.AddProject(additionalProjectId, projectState.Name, projectState.AssemblyName, projectState.Language);

for (var i = 0; i < projectState.Sources.Count; i++)
{
(var newFileName, var source) = sources[i];
var documentId = DocumentId.CreateNewId(additionalProjectId, debugName: newFileName);
solution = solution.AddDocument(documentId, newFileName, source);
}

solution = solution.AddProjectReference(projectId, new ProjectReference(additionalProjectId));
}

solution = solution.AddMetadataReferences(projectId, additionalMetadataReferences);

for (var i = 0; i < sources.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.Text;

namespace Microsoft.CodeAnalysis.Testing
{
public abstract class ProjectState
{
private readonly string _name;
private readonly string _defaultPrefix;
private readonly string _defaultExtension;

protected ProjectState(string name, string defaultPrefix, string defaultExtension)
{
_name = name;
_defaultPrefix = defaultPrefix;
_defaultExtension = defaultExtension;

Sources = new SourceFileList(defaultPrefix, defaultExtension);
}

public string Name { get; set; }

public string AssemblyName => Name;

public abstract string Language { get; }

/// <summary>
/// Gets the set of source files for analyzer or code fix testing. Files may be added to this list using one of
/// the <see cref="SourceFileList.Add(string)"/> methods.
/// </summary>
public SourceFileList Sources { get; }

public MetadataReferenceCollection AdditionalReferences { get; } = new MetadataReferenceCollection();
}
}
Loading

0 comments on commit aa93894

Please sign in to comment.