- 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:
Andre Rodrigues
2011-03-22 15:43:48 -07:00
parent b62e039f3a
commit 43a686da5c
5 changed files with 242 additions and 20 deletions

View File

@@ -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();

View File

@@ -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);
}
}
}
}

View File

@@ -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" />

View File

@@ -228,6 +228,8 @@ namespace Orchard.Tests.Stubs {
_clock = clock;
}
public FileEntry FileEntry { get { return _entry; } }
public override void Flush() {
throw new NotImplementedException();
}

View File

@@ -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) {