public IEnumerable <TestCase> Collect(IEnumerable <NPath> sourceFiles) { _rootDirectory.DirectoryMustExist(); _testCaseAssemblyPath.FileMustExist(); using (var caseAssemblyDefinition = AssemblyDefinition.ReadAssembly(_testCaseAssemblyPath.ToString())) { foreach (var file in sourceFiles) { if (CreateCase(caseAssemblyDefinition, file, out TestCase? testCase) && testCase != null) { yield return(testCase); } } } }
public static IReadOnlyCollection <PatchResult> PatchAllDependentAssemblies(NPath testAssemblyPath, PatchOptions patchOptions) { // TODO: ensure we do not have any assemblies that we want to patch already loaded // (this will require the separate in-memory patching ability) // this dll has types we're going to be injecting, so ensure it is in the same folder //var targetWeaverDll var toProcess = new List <NPath> { testAssemblyPath.FileMustExist() }; var patchResults = new Dictionary <string, PatchResult>(StringComparer.OrdinalIgnoreCase); var mockInjector = new MockInjector(testAssemblyPath.Parent.Combine("NSubstitute.Elevated")); for (var toProcessIndex = 0; toProcessIndex < toProcess.Count; ++toProcessIndex) { var assemblyToPatchPath = toProcess[toProcessIndex]; // as we accumulate dependencies recursively, we will probably get some duplicates we can early-out on if (patchResults.ContainsKey(assemblyToPatchPath)) { continue; } using (var assemblyToPatch = AssemblyDefinition.ReadAssembly(assemblyToPatchPath)) { GatherReferences(assemblyToPatchPath, assemblyToPatch); var patchResult = TryPatch(assemblyToPatchPath, assemblyToPatch); patchResults.Add(assemblyToPatchPath, patchResult); } } void GatherReferences(NPath assemblyToPatchPath, AssemblyDefinition assemblyToPatch) { foreach (var referencedAssembly in assemblyToPatch.Modules.SelectMany(m => m.AssemblyReferences)) { // only patch dll's we "own", that are in the same folder as the test assembly var referencedAssemblyPath = assemblyToPatchPath.Parent.Combine(referencedAssembly.Name + ".dll"); if (referencedAssemblyPath.FileExists()) { toProcess.Add(referencedAssemblyPath); } else if (!patchResults.ContainsKey(referencedAssembly.Name)) { patchResults.Add(referencedAssembly.Name, new PatchResult(referencedAssembly.Name, null, PatchState.IgnoredForeignAssembly)); } } } PatchResult TryPatch(NPath assemblyToPatchPath, AssemblyDefinition assemblyToPatch) { var alreadyPatched = mockInjector.IsPatched(assemblyToPatch); var cannotPatch = assemblyToPatch.Name.HasPublicKey; if (assemblyToPatchPath == testAssemblyPath && (patchOptions & PatchOptions.PatchTestAssembly) == 0) { if (alreadyPatched) { throw new Exception("Unexpected already-patched test assembly, yet we want PatchTestAssembly.No"); } if (cannotPatch) { throw new Exception("Cannot patch an assembly with a strong name"); } return(new PatchResult(assemblyToPatchPath, null, PatchState.IgnoredTestAssembly)); } if (alreadyPatched) { return(new PatchResult(assemblyToPatchPath, null, PatchState.AlreadyPatched)); } if (cannotPatch) { return(new PatchResult(assemblyToPatchPath, null, PatchState.IgnoredForeignAssembly)); } return(Patch(assemblyToPatchPath, assemblyToPatch)); } PatchResult Patch(NPath assemblyToPatchPath, AssemblyDefinition assemblyToPatch) { mockInjector.Patch(assemblyToPatch); // atomic write of file with backup // TODO: skip backup if existing file already patched. want the .orig to only be the unpatched file. // write to tmp and release the lock var tmpPath = assemblyToPatchPath.ChangeExtension(".tmp"); tmpPath.DeleteIfExists(); assemblyToPatch.Write(tmpPath); // $$$ , new WriterParameters { WriteSymbols = true }); see https://github.com/jbevain/cecil/issues/421 assemblyToPatch.Dispose(); if ((patchOptions & PatchOptions.SkipPeVerify) == 0) { PeVerify.Verify(tmpPath); } // move the actual file to backup, and move the tmp to actual var backupPath = GetPatchBackupPathFor(assemblyToPatchPath); File.Replace(tmpPath, assemblyToPatchPath, backupPath); // TODO: move pdb file too return(new PatchResult(assemblyToPatchPath, backupPath, PatchState.Patched)); } return(patchResults.Values); }