|
// 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 System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
#if DEBUG
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.Internal.Log;
#endif
namespace Microsoft.CodeAnalysis.Remote
{
internal static class TestUtils
{
public static void RemoveChecksums(this Dictionary<Checksum, object> map, ChecksumWithChildren checksums)
{
var set = new HashSet<Checksum>();
set.AppendChecksums(checksums);
RemoveChecksums(map, set);
}
public static void RemoveChecksums(this Dictionary<Checksum, object> map, IEnumerable<Checksum> checksums)
{
foreach (var checksum in checksums)
{
map.Remove(checksum);
}
}
internal static async Task AssertChecksumsAsync(
AssetProvider assetService,
Checksum checksumFromRequest,
Solution solutionFromScratch,
Solution incrementalSolutionBuilt)
{
#if DEBUG
var sb = new StringBuilder();
var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false);
var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false);
var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false);
// check 4 things
// 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update)
var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList();
AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb);
// 2. second check what items is mismatching for incremental solution
var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList();
AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb);
// 3. check whether solution created from scratch and incremental one have any mismatch
var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList();
AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb);
var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList();
AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb);
// 4. see what item is missing from request
var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false);
AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb);
var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false);
AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb);
var result = sb.ToString();
if (result.Length > 0)
{
Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result);
Debug.Fail("Differences detected in solution checksum: " + result);
}
return;
static void AppendMismatch(List<KeyValuePair<Checksum, object>> items, string title, StringBuilder stringBuilder)
{
if (items.Count == 0)
{
return;
}
stringBuilder.AppendLine(title);
foreach (var kv in items)
{
stringBuilder.AppendLine($"{kv.Key.ToString()}, {kv.Value.ToString()}");
}
stringBuilder.AppendLine();
}
async Task<List<KeyValuePair<Checksum, object>>> GetAssetFromAssetServiceAsync(IEnumerable<Checksum> checksums)
{
var items = new List<KeyValuePair<Checksum, object>>();
foreach (var checksum in checksums)
{
items.Add(new KeyValuePair<Checksum, object>(checksum, await assetService.GetAssetAsync<object>(checksum, CancellationToken.None).ConfigureAwait(false)));
}
return items;
}
async Task<HashSet<Checksum>> GetAllChildrenChecksumsAsync(Checksum solutionChecksum)
{
var set = new HashSet<Checksum>();
var solutionChecksums = await assetService.GetAssetAsync<SolutionStateChecksums>(solutionChecksum, CancellationToken.None).ConfigureAwait(false);
set.AppendChecksums(solutionChecksums);
foreach (var projectChecksum in solutionChecksums.Projects)
{
var projectChecksums = await assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, CancellationToken.None).ConfigureAwait(false);
set.AppendChecksums(projectChecksums);
foreach (var documentChecksum in projectChecksums.Documents.Concat(projectChecksums.AdditionalDocuments).Concat(projectChecksums.AnalyzerConfigDocuments))
{
var documentChecksums = await assetService.GetAssetAsync<DocumentStateChecksums>(documentChecksum, CancellationToken.None).ConfigureAwait(false);
set.AppendChecksums(documentChecksums);
}
}
return set;
}
#else
// have this to avoid error on async
await Task.CompletedTask.ConfigureAwait(false);
#endif
}
/// <summary>
/// create checksum to correspoing object map from solution
/// this map should contain every parts of solution that can be used to re-create the solution back
/// </summary>
public static async Task<Dictionary<Checksum, object>> GetAssetMapAsync(this Solution solution, CancellationToken cancellationToken)
{
var map = new Dictionary<Checksum, object>();
await solution.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false);
return map;
}
/// <summary>
/// create checksum to correspoing object map from project
/// this map should contain every parts of project that can be used to re-create the project back
/// </summary>
public static async Task<Dictionary<Checksum, object>> GetAssetMapAsync(this Project project, CancellationToken cancellationToken)
{
var map = new Dictionary<Checksum, object>();
await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false);
return map;
}
public static Task AppendAssetMapAsync(this Solution solution, Dictionary<Checksum, object> map, CancellationToken cancellationToken)
=> AppendAssetMapAsync(solution, map, projectId: null, cancellationToken);
public static async Task AppendAssetMapAsync(
this Solution solution, Dictionary<Checksum, object> map, ProjectId? projectId, CancellationToken cancellationToken)
{
if (projectId == null)
{
var solutionChecksums = await solution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false);
await solutionChecksums.FindAsync(solution.State, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false);
foreach (var project in solution.Projects)
await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false);
}
else
{
var solutionChecksums = await solution.State.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false);
await solutionChecksums.FindAsync(solution.State, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false);
var project = solution.GetRequiredProject(projectId);
await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false);
foreach (var dep in solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(projectId))
await solution.GetRequiredProject(dep).AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false);
}
}
private static async Task AppendAssetMapAsync(this Project project, Dictionary<Checksum, object> map, CancellationToken cancellationToken)
{
if (!RemoteSupportedLanguages.IsSupported(project.Language))
{
return;
}
var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false);
await projectChecksums.FindAsync(project.State, Flatten(projectChecksums), map, cancellationToken).ConfigureAwait(false);
foreach (var document in project.Documents.Concat(project.AdditionalDocuments).Concat(project.AnalyzerConfigDocuments))
{
var documentChecksums = await document.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false);
await documentChecksums.FindAsync(document.State, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false);
}
}
private static HashSet<Checksum> Flatten(ChecksumWithChildren checksums)
{
var set = new HashSet<Checksum>();
set.AppendChecksums(checksums);
return set;
}
public static void AppendChecksums(this HashSet<Checksum> set, ChecksumWithChildren checksums)
{
set.Add(checksums.Checksum);
foreach (var child in checksums.Children)
{
if (child is Checksum checksum)
{
if (checksum != Checksum.Null)
set.Add(checksum);
}
if (child is ChecksumCollection collection)
{
set.AppendChecksums(collection);
}
}
}
}
}
|