mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-12-03 03:58:13 +08:00
- Making project references on dynamic compilation also aware of pre-built DLL.
- Avoid duplicate references by using HashSet. - Adding some UT --HG-- branch : dev
This commit is contained in:
@@ -7,9 +7,7 @@ using Autofac;
|
||||
using Autofac.Core;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Records;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
@@ -27,7 +25,6 @@ namespace Orchard.Tests.Environment {
|
||||
private IEnumerable<ExtensionDescriptor> _extensionDescriptors;
|
||||
private IDictionary<string, IEnumerable<Type>> _featureTypes;
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void Init() {
|
||||
var builder = new ContainerBuilder();
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Autofac;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Environment.Extensions.Compilers;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tests.Stubs;
|
||||
|
||||
namespace Orchard.Tests.Environment.Loaders {
|
||||
[TestFixture]
|
||||
public class DynamicExtensionLoaderTests {
|
||||
private IContainer _container;
|
||||
private Mock<IProjectFileParser> _mockedStubProjectFileParser;
|
||||
private Mock<IDependenciesFolder> _mockedDependenciesFolder;
|
||||
|
||||
[SetUp]
|
||||
public void Init() {
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<DynamicExtensionLoaderAccessor>().As<DynamicExtensionLoaderAccessor>();
|
||||
|
||||
builder.RegisterType<DefaultBuildManager>().As<IBuildManager>();
|
||||
builder.RegisterType<DefaultAssemblyProbingFolder>().As<IAssemblyProbingFolder>();
|
||||
builder.RegisterType<StubHostEnvironment>().As<IHostEnvironment>();
|
||||
builder.RegisterType<StubVirtualPathMonitor>().As<IVirtualPathMonitor>();
|
||||
builder.RegisterType<StubVirtualPathProvider>().As<IVirtualPathProvider>();
|
||||
builder.RegisterType<DefaultAssemblyLoader>().As<IAssemblyLoader>();
|
||||
builder.RegisterType<StubClock>().As<IClock>();
|
||||
builder.RegisterType<StubAppDataFolder>().As<IAppDataFolder>();
|
||||
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
|
||||
|
||||
_mockedStubProjectFileParser = new Mock<IProjectFileParser>();
|
||||
builder.RegisterInstance(_mockedStubProjectFileParser.Object).As<IProjectFileParser>();
|
||||
builder.RegisterInstance(new StubFileSystem(new StubClock())).As<StubFileSystem>();
|
||||
|
||||
_mockedDependenciesFolder = new Mock<IDependenciesFolder>();
|
||||
builder.RegisterInstance(_mockedDependenciesFolder.Object).As<IDependenciesFolder>();
|
||||
|
||||
_container = builder.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDependenciesContainsNoDuplicatesTest() {
|
||||
const string fileName1 = "a.cs";
|
||||
const string fileName2 = "b.cs";
|
||||
|
||||
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
|
||||
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
|
||||
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
|
||||
|
||||
// Create duplicate source files (invalid situation in reality but easy enough to test)
|
||||
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.IsAny<Stream>())).Returns(
|
||||
new ProjectFileDescriptor { SourceFilenames = new[] { fileName1, fileName2, fileName1 } }); // duplicate file
|
||||
|
||||
IEnumerable<string> dependencies = extensionLoader.GetDependenciesAccessor(fileEntry.Name);
|
||||
|
||||
Assert.That(dependencies.Count(), Is.EqualTo(3), "3 results should mean no duplicates");
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDependenciesContainsNoDuplicatesEvenIfMultipleProjectsTest() {
|
||||
const string fileName1 = "a.cs";
|
||||
const string fileName2 = "b.cs";
|
||||
|
||||
const string commonFileName = "c.cs";
|
||||
|
||||
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
|
||||
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
|
||||
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
|
||||
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj");
|
||||
StubFileSystem.FileEntry fileEntry3 = stubFileSystem.CreateFileEntry("orchard.c.csjproj");
|
||||
|
||||
// Project a reference b and c which share a file in common
|
||||
|
||||
// Result for project a
|
||||
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry)))
|
||||
.Returns(
|
||||
new ProjectFileDescriptor {
|
||||
SourceFilenames = new[] { fileName1, fileName2 },
|
||||
References = new[] {
|
||||
new ReferenceDescriptor {
|
||||
ReferenceType = ReferenceType.Project,
|
||||
SimpleName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
FullName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
Path = fileEntry2.Name
|
||||
},
|
||||
new ReferenceDescriptor {
|
||||
ReferenceType = ReferenceType.Project,
|
||||
SimpleName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
FullName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
Path = fileEntry3.Name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Result for project b and c
|
||||
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream =>
|
||||
((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2 || ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry3)))
|
||||
.Returns(
|
||||
new ProjectFileDescriptor {
|
||||
SourceFilenames = new[] { commonFileName }
|
||||
});
|
||||
|
||||
IEnumerable<string> dependencies = extensionLoader.GetDependenciesAccessor(fileEntry.Name);
|
||||
|
||||
Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates");
|
||||
|
||||
// Project files
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry3.Name)), Is.Not.Null);
|
||||
|
||||
// Individual source files
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDependenciesContainsBinForReferencedProjectsTest() {
|
||||
const string fileName1 = "a.cs";
|
||||
const string fileName2 = "b.cs";
|
||||
|
||||
const string commonFileName = "c.cs";
|
||||
|
||||
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
|
||||
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
|
||||
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
|
||||
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj");
|
||||
|
||||
StubFileSystem.DirectoryEntry directoryEntry = stubFileSystem.CreateDirectoryEntry("bin");
|
||||
StubFileSystem.FileEntry fileEntry3 = directoryEntry.CreateFile("orchard.b.dll");
|
||||
|
||||
// Project a reference b and c which share a file in common
|
||||
|
||||
// Result for project a
|
||||
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry)))
|
||||
.Returns(
|
||||
new ProjectFileDescriptor {
|
||||
SourceFilenames = new[] { fileName1, fileName2 },
|
||||
References = new[] {
|
||||
new ReferenceDescriptor {
|
||||
ReferenceType = ReferenceType.Project,
|
||||
SimpleName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
FullName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
|
||||
Path = fileEntry2.Name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Result for project b and c
|
||||
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream =>
|
||||
((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2)))
|
||||
.Returns(
|
||||
new ProjectFileDescriptor {
|
||||
SourceFilenames = new[] { commonFileName }
|
||||
});
|
||||
|
||||
_mockedDependenciesFolder.Setup(dependenciesFolder => dependenciesFolder.GetDescriptor(It.Is<string>(moduleName => moduleName == Path.GetDirectoryName(fileEntry2.Name))))
|
||||
.Returns(
|
||||
new DependencyDescriptor {
|
||||
VirtualPath = Path.Combine(directoryEntry.Name, fileEntry3.Name)
|
||||
});
|
||||
|
||||
IEnumerable<string> dependencies = extensionLoader.GetDependenciesAccessor(fileEntry.Name);
|
||||
|
||||
Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates");
|
||||
|
||||
// Project files
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(Path.Combine(directoryEntry.Name, fileEntry3.Name))), Is.Not.Null);
|
||||
|
||||
// Individual source files
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null);
|
||||
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null);
|
||||
}
|
||||
|
||||
internal class DynamicExtensionLoaderAccessor : DynamicExtensionLoader {
|
||||
public DynamicExtensionLoaderAccessor(
|
||||
IBuildManager buildManager,
|
||||
IVirtualPathProvider virtualPathProvider,
|
||||
IVirtualPathMonitor virtualPathMonitor,
|
||||
IHostEnvironment hostEnvironment,
|
||||
IAssemblyProbingFolder assemblyProbingFolder,
|
||||
IDependenciesFolder dependenciesFolder,
|
||||
IProjectFileParser projectFileParser)
|
||||
: base(buildManager, virtualPathProvider, virtualPathMonitor, hostEnvironment, assemblyProbingFolder, dependenciesFolder, projectFileParser) {}
|
||||
|
||||
public IEnumerable<string> GetDependenciesAccessor(string projectPath) {
|
||||
return GetDependencies(projectPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,7 @@
|
||||
<Compile Include="Environment\DefaultWorkContextAccessorTests.cs" />
|
||||
<Compile Include="Environment\Extensions\ExtensionLoaderCoordinatorTests.cs" />
|
||||
<Compile Include="Environment\Features\FeatureManagerTests.cs" />
|
||||
<Compile Include="Environment\Loaders\DynamicExtensionLoaderTests.cs" />
|
||||
<Compile Include="Environment\State\DefaultProcessingEngineTests.cs" />
|
||||
<Compile Include="Environment\RunningShellTableTests.cs" />
|
||||
<Compile Include="Environment\StubHostEnvironment.cs" />
|
||||
|
||||
@@ -228,6 +228,8 @@ namespace Orchard.Tests.Stubs {
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
public FileEntry FileEntry { get { return _entry; } }
|
||||
|
||||
public override void Flush() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
private readonly IVirtualPathMonitor _virtualPathMonitor;
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
private readonly IAssemblyProbingFolder _assemblyProbingFolder;
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IProjectFileParser _projectFileParser;
|
||||
private readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround();
|
||||
|
||||
@@ -37,6 +38,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_assemblyProbingFolder = assemblyProbingFolder;
|
||||
_projectFileParser = projectFileParser;
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
@@ -176,34 +178,48 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetDependencies(string projectPath) {
|
||||
List<string> dependencies = new[] { projectPath }.ToList();
|
||||
protected IEnumerable<string> GetDependencies(string projectPath) {
|
||||
HashSet<string> dependencies = new HashSet<string> { projectPath };
|
||||
|
||||
var basePath = _virtualPathProvider.GetDirectoryName(projectPath);
|
||||
AddDependencies(projectPath, dependencies);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void AddDependencies(string projectPath, HashSet<string> currentSet) {
|
||||
string basePath = _virtualPathProvider.GetDirectoryName(projectPath);
|
||||
|
||||
using (var stream = _virtualPathProvider.OpenFile(projectPath)) {
|
||||
var projectFile = _projectFileParser.Parse(stream);
|
||||
ProjectFileDescriptor projectFile = _projectFileParser.Parse(stream);
|
||||
|
||||
// Add source files
|
||||
dependencies.AddRange(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f)));
|
||||
currentSet.UnionWith(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f)));
|
||||
|
||||
// Add Project and Library References
|
||||
foreach (ReferenceDescriptor referenceDescriptor in projectFile.References.Where(reference => !string.IsNullOrEmpty(reference.Path))) {
|
||||
string path = referenceDescriptor.ReferenceType == ReferenceType.Library
|
||||
? _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, referenceDescriptor.SimpleName, referenceDescriptor.Path)
|
||||
: _virtualPathProvider.Combine(basePath, referenceDescriptor.Path);
|
||||
// Add Project and Library references
|
||||
if (projectFile.References != null) {
|
||||
foreach (ReferenceDescriptor referenceDescriptor in projectFile.References.Where(reference => !string.IsNullOrEmpty(reference.Path))) {
|
||||
string path = referenceDescriptor.ReferenceType == ReferenceType.Library
|
||||
? _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, referenceDescriptor.SimpleName, referenceDescriptor.Path)
|
||||
: _virtualPathProvider.Combine(basePath, referenceDescriptor.Path);
|
||||
|
||||
if (_virtualPathProvider.FileExists(path)) {
|
||||
dependencies.Add(path);
|
||||
// Attempt to reference the project / library file
|
||||
if (!currentSet.Contains(path) && _virtualPathProvider.FileExists(path)) {
|
||||
currentSet.Add(path);
|
||||
|
||||
if (referenceDescriptor.ReferenceType == ReferenceType.Project) {
|
||||
dependencies.AddRange(GetDependencies(path));
|
||||
// In case of project, also reference the source files
|
||||
if (referenceDescriptor.ReferenceType == ReferenceType.Project) {
|
||||
AddDependencies(path, currentSet);
|
||||
|
||||
// Try to also reference any pre-built DLL
|
||||
DependencyDescriptor dependencyDescriptor = _dependenciesFolder.GetDescriptor(_virtualPathProvider.GetDirectoryName(referenceDescriptor.Path));
|
||||
if (dependencyDescriptor != null && _virtualPathProvider.FileExists(dependencyDescriptor.VirtualPath)) {
|
||||
currentSet.Add(dependencyDescriptor.VirtualPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private string GetProjectPath(ExtensionDescriptor descriptor) {
|
||||
@@ -226,7 +242,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// The purpose of this class is to keep track of all .csproj files monitored until
|
||||
/// an AppDomain restart.
|
||||
/// </summary>
|
||||
class ReloadWorkaround {
|
||||
internal class ReloadWorkaround {
|
||||
private readonly List<IVolatileToken> _tokens = new List<IVolatileToken>();
|
||||
|
||||
public void Monitor(IVolatileToken whenProjectFileChanges) {
|
||||
|
||||
Reference in New Issue
Block a user