// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using NuGet.Frameworks; using NuGet.ProjectManagement; using NuGet.Test.Utility; namespace Test.Utility { public class TestMSBuildNuGetProjectSystem : IMSBuildProjectSystem { private const string TestProjectName = "TestProjectName"; public Dictionary References { get; } public HashSet FrameworkReferences { get; } public HashSet Files { get; } private HashSet _filesInProcessing; public HashSet ProcessedFiles { get; private set; } public HashSet Imports { get; } public int BindingRedirectsCallCount { get; private set; } public INuGetProjectContext NuGetProjectContext { get; set; } public int BatchCount { get; private set; } public Action AddReferenceAction { get; set; } public Action RemoveReferenceAction { get; set; } private Dictionary KnownProperties { get; } public TestMSBuildNuGetProjectSystem( NuGetFramework targetFramework, INuGetProjectContext nuGetProjectContext, string projectFullPath = null, string projectName = null, string projectFileFullPath = null) { TargetFramework = targetFramework; References = new Dictionary(); FrameworkReferences = new HashSet(); Files = new HashSet(); Imports = new HashSet(); NuGetProjectContext = nuGetProjectContext; ProjectFullPath = string.IsNullOrEmpty(projectFullPath) ? Environment.CurrentDirectory : projectFullPath; ProcessedFiles = new HashSet(); ProjectName = projectName ?? TestProjectName; ProjectFileFullPath = projectFileFullPath ?? Path.Combine(ProjectFullPath, ProjectName); AddReferenceAction = AddReferenceImplementation; RemoveReferenceAction = RemoveReferenceImplementation; KnownProperties = new Dictionary() { { "NuGetLockFilePath", null }, { "RestorePackagesWithLockFile", "false" } }; } public void AddFile(string path, Stream stream) { if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(Path.GetFileName(path))) { return; } using (var streamReader = new StreamReader(stream)) { Files.Add(path); var fullPath = Path.Combine(ProjectFullPath, path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); File.WriteAllText(fullPath, streamReader.ReadToEnd()); } } public Task AddFrameworkReferenceAsync(string name, string packageId) { if (FrameworkReferences.Contains(name)) { throw new InvalidOperationException("Cannot add existing reference. That would be a COMException in VS"); } FrameworkReferences.Add(name); return Task.CompletedTask; } public void AddImport(string targetFullPath, ImportLocation location) { Imports.Add(targetFullPath); } public Task AddReferenceAsync(string referencePath) { AddReferenceAction(referencePath); return Task.CompletedTask; } public void RemoveFile(string path) { Files.Remove(path); var fullPath = Path.Combine(ProjectFullPath, path); if (File.Exists(fullPath)) { File.Delete(fullPath); } } public string ProjectFullPath { get; } public string ProjectFileFullPath { get; } public string ProjectName { get; } public string ProjectUniqueName { get { return ProjectName; } } public Task ReferenceExistsAsync(string name) { return Task.FromResult(References.ContainsKey(name) || FrameworkReferences.Contains(name)); } public void RemoveImport(string targetFullPath) { Imports.Remove(targetFullPath); } public Task RemoveReferenceAsync(string name) { RemoveReferenceAction(name); return Task.CompletedTask; } public NuGetFramework TargetFramework { get; } public bool FileExistsInProject(string path) { return Files.Any(c => path.Equals(c, StringComparison.OrdinalIgnoreCase)); } public void SetPropertyValue(string propertyName, dynamic value) { KnownProperties[propertyName] = value; } public dynamic GetPropertyValue(string propertyName) { if (KnownProperties.TryGetValue(propertyName, out var value)) { return value; } // Real builds have lots of properties added by the project file and maybe the build system. Returning null here to // signal an undefined property might not match real-world results of this method, so throw an exception so tests // can't accidently rely on test-only behaviour. throw new NotImplementedException(); } public string ResolvePath(string path) { return path; } public bool IsSupportedFile(string path) { return true; } public void AddExistingFile(string path) { Files.Add(path); } public void AddBindingRedirects() { BindingRedirectsCallCount++; } public Task BeginProcessingAsync() { return Task.CompletedTask; } public void RegisterProcessedFiles(IEnumerable files) { if (_filesInProcessing == null) { _filesInProcessing = new HashSet(files); } foreach (var file in files) { _filesInProcessing.Add(file); } } public Task EndProcessingAsync() { ++BatchCount; ProcessedFiles = _filesInProcessing; _filesInProcessing = null; return Task.CompletedTask; } public void DeleteDirectory(string path, bool recursive) { var fullPath = Path.Combine(ProjectFullPath, path); // this will delete recursive regadless of input, doesn't matter for testing TestFileSystemUtility.DeleteRandomTestFolder(fullPath); } public IEnumerable GetFiles(string path, string filter, bool recursive) { return Files.Where(f => f.StartsWith(path)); } public IEnumerable GetFullPaths(string fileName) { return Files.Where(f => f.Equals(fileName, StringComparison.OrdinalIgnoreCase)) .Select(f => Path.Combine(ProjectFullPath, f)); } public IEnumerable GetDirectories(string path) { return GetFiles(path, "*.*", recursive: true); } private void AddReferenceImplementation(string referencePath) { var referenceAssemblyName = Path.GetFileName(referencePath); if (References.ContainsKey(referenceAssemblyName)) { throw new InvalidOperationException("Cannot add existing reference. That would be a COMException in VS"); } References.Add(referenceAssemblyName, referencePath); } private void RemoveReferenceImplementation(string name) { if (References.ContainsKey(name)) { References.Remove(name); } } } }