예제 #1
0
        public void TempCleanerCleanRootDirectory(bool deleteRoot)
        {
            string moveDeletionTemp = Path.Combine(TemporaryDirectory, "MoveDeletionTemp");

            Directory.CreateDirectory(moveDeletionTemp);

            string directoryToBeDeleted = Path.Combine(TemporaryDirectory, Guid.NewGuid().ToString());

            Directory.CreateDirectory(directoryToBeDeleted);
            string deletedFile = Path.Combine(directoryToBeDeleted, "file");

            File.WriteAllText(deletedFile, "asdf");

            string movedFile = Path.Combine(TemporaryDirectory, "movedFile");

            File.WriteAllText(movedFile, "asdf");

            // Create a temp cleaner with a temp directory
            using (TempCleaner cleaner = new TempCleaner(moveDeletionTemp))
            {
                // Move-delete a file into the TempCleaner temp directory
                FileUtilities.TryMoveDelete(movedFile, cleaner.TempDirectory);

                cleaner.RegisterDirectoryToDelete(directoryToBeDeleted, deleteRoot);

                cleaner.WaitPendingTasksForCompletion();
            }

            XAssert.AreEqual(!deleteRoot, Directory.Exists(directoryToBeDeleted));
            XAssert.IsTrue(Directory.Exists(moveDeletionTemp));
        }
예제 #2
0
        public void CleanTempCleanerTempDirectory()
        {
            // Make a temp directory for TempCleaner
            string deletionTemp = Path.Combine(TemporaryDirectory, "DeletionTemp");

            Directory.CreateDirectory(deletionTemp);

            // Make a file outside of TempCleaner temp directory
            string deletedFile = Path.Combine(TemporaryDirectory, Guid.NewGuid().ToString());

            File.WriteAllText(deletedFile, "asdf");

            // Create a temp cleaner with a temp directory
            using (TempCleaner cleaner = new TempCleaner(deletionTemp))
            {
                // Move-delete a file into the TempCleaner temp directory
                FileUtilities.TryMoveDelete(deletedFile, cleaner.TempDirectory);

                cleaner.WaitPendingTasksForCompletion();
                XAssert.AreEqual(1, cleaner.SucceededDirectories);
            }

            // TempCleaner should clean out \deletionTemp before or during Dispose
            XAssert.IsFalse(File.Exists(deletedFile));
        }
예제 #3
0
        public TestScheduler(
            PipGraph graph,
            TestPipQueue pipQueue,
            PipExecutionContext context,
            FileContentTable fileContentTable,
            EngineCache cache,
            IConfiguration configuration,
            FileAccessWhitelist fileAccessWhitelist,
            DirectoryMembershipFingerprinterRuleSet directoryMembershipFingerprinterRules = null,
            TempCleaner tempCleaner = null,
            PipRuntimeTimeTable runningTimeTable      = null,
            JournalState journalState                 = null,
            PerformanceCollector performanceCollector = null,
            string fingerprintSalt                  = null,
            ContentHash?previousInputsSalt          = null,
            IEnumerable <Pip> successfulPips        = null,
            IEnumerable <Pip> failedPips            = null,
            LoggingContext loggingContext           = null,
            IIpcProvider ipcProvider                = null,
            DirectoryTranslator directoryTranslator = null,
            VmInitializer vmInitializer             = null,
            SchedulerTestHooks testHooks            = null) : base(graph, pipQueue, context, fileContentTable, cache,
                                                                   configuration, fileAccessWhitelist, loggingContext, null, directoryMembershipFingerprinterRules,
                                                                   tempCleaner, Task.FromResult <PipRuntimeTimeTable>(runningTimeTable), performanceCollector, fingerprintSalt, previousInputsSalt,
                                                                   ipcProvider: ipcProvider,
                                                                   directoryTranslator: directoryTranslator,
                                                                   journalState: journalState,
                                                                   vmInitializer: vmInitializer,
                                                                   testHooks: testHooks)
        {
            m_testPipQueue = pipQueue;

            if (successfulPips != null)
            {
                foreach (var pip in successfulPips)
                {
                    Contract.Assume(pip.PipId.IsValid, "Override results must be added after the pip has been added to the scheduler");
                    m_overridePipResults.Add(pip.PipId, PipResultStatus.Succeeded);
                }
            }

            if (failedPips != null)
            {
                foreach (var pip in failedPips)
                {
                    Contract.Assume(pip.PipId.IsValid, "Override results must be added after the pip has been added to the scheduler");
                    m_overridePipResults.Add(pip.PipId, PipResultStatus.Failed);
                }
            }

            m_loggingContext = loggingContext;
        }
예제 #4
0
 public static void Register(string dir)
 {
     try
     {
         if (!_WatchedDirectories.ContainsKey(dir))
         {
             lock (_WatchedDirectories)
                 if (!_WatchedDirectories.ContainsKey(dir))
                 {
                     TempCleaner c = new TempCleaner(dir);
                     _CleanerPool.BeginAsync(c);
                     _WatchedDirectories[dir] = dir;
                 }
         }
     }
     catch { }
 }
예제 #5
0
        public void CleanNonexisting()
        {
            RegisterEventSource(global::BuildXL.Scheduler.ETWLogger.Log);

            string baseDir = TemporaryDirectory;
            string dir     = Path.Combine(baseDir, "DirDoesntExist");
            string file    = Path.Combine(baseDir, "FileDoesntExist.txt");

            using (TempCleaner cleaner = new TempCleaner())
            {
                cleaner.RegisterDirectoryToDelete(dir, deleteRootDirectory: false);
                cleaner.RegisterFileToDelete(file);
                cleaner.WaitPendingTasksForCompletion();
            }

            // No warnings for missing file/folder
            m_expectedWarningCount = 0;
        }
예제 #6
0
        public void CleanTempDirsAndFilesWithOpenedFile()
        {
            // Arrange
            Tuple <string, string> dirs = CreateDirs();

            string file = Path.Combine(dirs.Item1, "openedFile.txt");

            // Warning for a failed deleted file
            IgnoreWarnings();

            using (StreamWriter writer = new StreamWriter(file))
            {
                writer.Write("asdf");
                writer.Flush();
                XAssert.IsTrue(File.Exists(file));

                // Act
                using (TempCleaner cleaner = new TempCleaner())
                {
                    // This should fail
                    cleaner.RegisterDirectoryToDelete(dirs.Item1, deleteRootDirectory: false);
                    cleaner.RegisterDirectoryToDelete(dirs.Item2, deleteRootDirectory: false);

                    // This should fail
                    cleaner.RegisterFileToDelete(file);

                    // Waiting for pending tasks to complete
                    cleaner.WaitPendingTasksForCompletion();

                    // Assert
                    XAssert.AreEqual(1, cleaner.SucceededDirectories);
                    XAssert.AreEqual(0, cleaner.PendingDirectories);
                    XAssert.AreEqual(1, cleaner.FailedDirectories);

                    XAssert.AreEqual(0, cleaner.SucceededFiles);
                    XAssert.AreEqual(0, cleaner.PendingFiles);
                    XAssert.AreEqual(1, cleaner.FailedFiles);
                }

                XAssert.IsTrue(File.Exists(file));
            }

            AssertDirectoryEmpty(dirs.Item2);
        }
예제 #7
0
        public void CheckCleanTempDirDependingOnPipResult(bool shouldPipFail)
        {
            Configuration.Engine.CleanTempDirectories = true;

            var tempdir    = DirectoryArtifact.CreateWithZeroPartialSealId(CreateUniqueDirectory());
            var tempdirStr = ArtifactToString(tempdir);
            var file       = CreateOutputFileArtifact(tempdirStr);
            var fileStr    = ArtifactToString(file);
            var operations = new List <Operation>()
            {
                Operation.WriteFile(CreateOutputFileArtifact()),
                Operation.WriteFile(file, doNotInfer: true),
            };

            if (shouldPipFail)
            {
                operations.Add(Operation.Fail());
            }

            var builder = CreatePipBuilder(operations);

            builder.SetTempDirectory(tempdir);
            SchedulePipBuilder(builder);

            using (var tempCleaner = new TempCleaner())
            {
                if (shouldPipFail)
                {
                    RunScheduler(tempCleaner: tempCleaner).AssertFailure();
                    AssertErrorEventLogged(EventId.PipProcessError);
                    tempCleaner.WaitPendingTasksForCompletion();
                    XAssert.IsTrue(Directory.Exists(tempdirStr), $"TEMP directory deleted but wasn't supposed to: {tempdirStr}");
                    XAssert.IsTrue(File.Exists(fileStr), $"Temp file deleted but wasn't supposed to: {fileStr}");
                }
                else
                {
                    RunScheduler(tempCleaner: tempCleaner).AssertSuccess();
                    tempCleaner.WaitPendingTasksForCompletion();
                    XAssert.IsFalse(File.Exists(fileStr), $"Temp file not deleted: {fileStr}");
                    XAssert.IsFalse(Directory.Exists(tempdirStr), $"TEMP directory not deleted: {tempdirStr}");
                }
            }
        }
예제 #8
0
        public void CleanTempDirsAndFiles()
        {
            // Arrange
            Tuple <string, string> dirs = CreateDirs();

            string tempFile = Path.Combine(dirs.Item1, "SomeTemporaryFile.txt");

            PrepareFileWithConent(tempFile, "sampleContent");

            // Act
            using (TempCleaner cleaner = new TempCleaner())
            {
                cleaner.RegisterDirectoryToDelete(dirs.Item1, deleteRootDirectory: false);
                cleaner.RegisterDirectoryToDelete(dirs.Item2, deleteRootDirectory: false);
                cleaner.RegisterFileToDelete(tempFile);

                // Registering file that is not exists
                string notExistedPath = Path.Combine(dirs.Item1, "UnknownFile.txt");
                XAssert.IsFalse(File.Exists(notExistedPath));
                cleaner.RegisterFileToDelete(notExistedPath);

                // Waiting for pending tasks to complete
                cleaner.WaitPendingTasksForCompletion();

                // Assert
                XAssert.AreEqual(2, cleaner.SucceededDirectories);
                XAssert.AreEqual(0, cleaner.PendingDirectories);
                XAssert.AreEqual(0, cleaner.FailedDirectories);

                XAssert.AreEqual(2, cleaner.SucceededFiles);
                XAssert.AreEqual(0, cleaner.PendingFiles);
                XAssert.AreEqual(0, cleaner.FailedFiles);
            }

            AssertDirectoryEmpty(dirs.Item1);
            AssertDirectoryEmpty(dirs.Item2);
        }
예제 #9
0
        /// <summary>
        /// Runs the scheduler allowing various options to be specifically set
        /// </summary>
        public ScheduleRunResult RunSchedulerSpecific(
            PipGraph graph,
            SchedulerTestHooks testHooks  = null,
            SchedulerState schedulerState = null,
            RootFilter filter             = null,
            TempCleaner tempCleaner       = null)
        {
            var config = new CommandLineConfiguration(Configuration);

            // Populating the configuration may modify the configuration, so it should occur first.
            BuildXLEngine.PopulateLoggingAndLayoutConfiguration(config, Context.PathTable, bxlExeLocation: null, inTestMode: true);
            BuildXLEngine.PopulateAndValidateConfiguration(config, config, Context.PathTable, LoggingContext);

            FileAccessWhitelist whitelist = new FileAccessWhitelist(Context);

            whitelist.Initialize(config);

            IReadOnlyList <string> junctionRoots = Configuration.Engine.DirectoriesToTranslate?.Select(a => a.ToPath.ToString(Context.PathTable)).ToList();

            var map = VolumeMap.TryCreateMapOfAllLocalVolumes(LoggingContext, junctionRoots);
            var optionalAccessor = TryGetJournalAccessor(map);

            // Although scan change journal is enabled, but if we cannot create an enabled journal accessor, then create a disabled one.
            m_journalState = map == null || !optionalAccessor.IsValid
                ? JournalState.DisabledJournal
                : JournalState.CreateEnabledJournal(map, optionalAccessor.Value);

            if (config.Schedule.IncrementalScheduling)
            {
                // Ensure that we can scan the journal when incremental scheduling is enabled.
                XAssert.IsTrue(m_journalState.IsEnabled, "Incremental scheduling requires that journal is enabled");
            }

            // Seal the translator if not sealed
            DirectoryTranslator.Seal();

            // .....................................................................................
            // some dummy setup in order to get a PreserveOutputsSalt.txt file and an actual salt
            // .....................................................................................
            string dummyCacheDir = Path.Combine(TemporaryDirectory, "Out", "Cache");

            Directory.CreateDirectory(dummyCacheDir); // EngineSchedule tries to put the PreserveOutputsSalt.txt here
            ContentHash?previousOutputsSalt =
                EngineSchedule.PreparePreviousOutputsSalt(LoggingContext, Context.PathTable, config);

            Contract.Assert(previousOutputsSalt.HasValue);
            // .....................................................................................

            testHooks = testHooks ?? new SchedulerTestHooks();
            Contract.Assert(!(config.Engine.CleanTempDirectories && tempCleaner == null));

            using (var queue = new PipQueue(config.Schedule))
                using (var testQueue = new TestPipQueue(queue, LoggingContext, initiallyPaused: false))
                    using (var testScheduler = new TestScheduler(
                               graph: graph,
                               pipQueue: testQueue,
                               context: Context,
                               fileContentTable: FileContentTable,
                               loggingContext: LoggingContext,
                               cache: Cache,
                               configuration: config,
                               journalState: m_journalState,
                               fileAccessWhitelist: whitelist,
                               fingerprintSalt: Configuration.Cache.CacheSalt,
                               directoryMembershipFingerprinterRules: new DirectoryMembershipFingerprinterRuleSet(Configuration, Context.StringTable),
                               tempCleaner: tempCleaner,
                               previousInputsSalt: previousOutputsSalt.Value,
                               successfulPips: null,
                               failedPips: null,
                               ipcProvider: null,
                               directoryTranslator: DirectoryTranslator,
                               testHooks: testHooks))
                    {
                        if (filter == null)
                        {
                            EngineSchedule.TryGetPipFilter(LoggingContext, Context, config, config, Expander.TryGetRootByMountName, out filter);
                        }

                        XAssert.IsTrue(testScheduler.InitForMaster(LoggingContext, filter, schedulerState), "Failed to initialized test scheduler");

                        testScheduler.Start(LoggingContext);

                        bool success = testScheduler.WhenDone().GetAwaiter().GetResult();
                        testScheduler.SaveFileChangeTrackerAsync(LoggingContext).Wait();

                        return(new ScheduleRunResult
                        {
                            Graph = graph,
                            Config = config,
                            Success = success,
                            PipResults = testScheduler.PipResults,
                            PipExecutorCounters = testScheduler.PipExecutionCounters,
                            PathSets = testScheduler.PathSets,
                            ProcessPipCountersByFilter = testScheduler.ProcessPipCountersByFilter,
                            ProcessPipCountersByTelemetryTag = testScheduler.ProcessPipCountersByTelemetryTag,
                            SchedulerState = new SchedulerState(testScheduler)
                        });
                    }
        }
예제 #10
0
        /// <summary>
        /// Runs the scheduler using the instance member PipGraph and Configuration objects. This will also carry over
        /// any state from any previous run such as the cache
        /// </summary>
        public ScheduleRunResult RunScheduler(SchedulerTestHooks testHooks = null, SchedulerState schedulerState = null, RootFilter filter = null, TempCleaner tempCleaner = null)
        {
            if (m_graphWasModified || m_lastGraph == null)
            {
                m_lastGraph = PipGraphBuilder.Build();
                XAssert.IsNotNull(m_lastGraph, "Failed to build pip graph");
            }

            m_graphWasModified = false;
            return(RunSchedulerSpecific(m_lastGraph, testHooks, schedulerState, filter, tempCleaner));
        }
 /// <summary>
 /// Runs the scheduler using the instance member PipGraph and Configuration objects. This will also carry over
 /// any state from any previous run such as the cache
 /// </summary>
 public ScheduleRunResult RunScheduler(SchedulerTestHooks testHooks = null, SchedulerState schedulerState = null, RootFilter filter = null, TempCleaner tempCleaner = null, IEnumerable <(Pip before, Pip after)> constraintExecutionOrder = null)
예제 #12
0
        static void _Act(string source, string destination, FileExistsAction ifExists, out string destinationFilename, int retryCount, int retryDelaySeconds, bool useTempHop, bool isMove)
        {
            if (String.IsNullOrEmpty(source))
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (String.IsNullOrEmpty(destination))
            {
                throw new ArgumentNullException(nameof(destination));
            }

            source      = STEM.Sys.IO.Path.ChangeIpToMachineName(STEM.Sys.IO.Path.AdjustPath(source));
            destination = STEM.Sys.IO.Path.ChangeIpToMachineName(STEM.Sys.IO.Path.AdjustPath(destination));

            if (source.Equals(destination, StringComparison.InvariantCultureIgnoreCase))
            {
                destinationFilename = destination;
                return;
            }

            destinationFilename = "";

            if (isMove)
            {
                string sourceAddress      = STEM.Sys.IO.Net.MachineAddress(STEM.Sys.IO.Path.FirstTokenOfPath(source));
                string destinationAddress = STEM.Sys.IO.Net.MachineAddress(STEM.Sys.IO.Path.FirstTokenOfPath(destination));

                if (sourceAddress == destinationAddress)
                {
                    string sp = STEM.Sys.IO.Path.AdjustPath(source).TrimStart('\\').TrimStart('/');

                    if (sp.IndexOf(System.IO.Path.DirectorySeparatorChar) != -1)
                    {
                        sp = sp.Substring(sp.IndexOf(System.IO.Path.DirectorySeparatorChar));
                    }

                    sp = STEM.Sys.IO.Path.FirstTokenOfPath(sp);

                    string dp = STEM.Sys.IO.Path.AdjustPath(destination).TrimStart('\\').TrimStart('/');

                    if (dp.IndexOf(System.IO.Path.DirectorySeparatorChar) != -1)
                    {
                        dp = dp.Substring(dp.IndexOf(System.IO.Path.DirectorySeparatorChar));
                    }

                    dp = STEM.Sys.IO.Path.FirstTokenOfPath(dp);

                    if (sp.Equals(dp, StringComparison.InvariantCultureIgnoreCase))
                    {
                        useTempHop = false;
                    }
                }
            }

            while (retryCount >= 0)
            {
                if (!System.IO.File.Exists(source))
                {
                    throw new System.IO.FileNotFoundException("Could not find file " + source);
                }

                string d = destination;

                if (useTempHop)
                {
                    string dTemp = System.IO.Path.Combine(STEM.Sys.IO.Path.GetDirectoryName(d), "Temp");

                    if (!System.IO.Directory.Exists(dTemp))
                    {
                        System.IO.Directory.CreateDirectory(dTemp);
                    }

                    TempCleaner.Register(dTemp);
                }
                else
                {
                    if (!System.IO.Directory.Exists(STEM.Sys.IO.Path.GetDirectoryName(d)))
                    {
                        System.IO.Directory.CreateDirectory(STEM.Sys.IO.Path.GetDirectoryName(d));
                    }
                }

                try
                {
                    if (useTempHop)
                    {
                        string dTemp = System.IO.Path.Combine(STEM.Sys.IO.Path.GetDirectoryName(d), "Temp");

                        if (!System.IO.Directory.Exists(dTemp))
                        {
                            System.IO.Directory.CreateDirectory(dTemp);
                        }

                        dTemp = System.IO.Path.Combine(dTemp, Guid.NewGuid() + ".tmp");

                        try
                        {
                            if (System.IO.File.Exists(d))
                            {
                                switch (ifExists)
                                {
                                case FileExistsAction.Throw:
                                    throw new System.IO.IOException("Destination file already exists: " + destination);

                                case FileExistsAction.Skip:
                                    return;

                                case FileExistsAction.Overwrite:

                                    System.IO.File.Copy(source, dTemp, true);

                                    try
                                    {
                                        System.IO.File.SetAttributes(d, System.IO.FileAttributes.Normal);
                                        System.IO.File.Copy(dTemp, d, true);
                                    }
                                    catch (Exception ex)
                                    {
                                        throw new System.IO.IOException("Destination file could not be overwritten: " + d, ex);
                                    }

                                    break;

                                case FileExistsAction.OverwriteIfNewer:

                                    if (System.IO.File.GetLastWriteTimeUtc(d) >= System.IO.File.GetLastWriteTimeUtc(source))
                                    {
                                        return;
                                    }

                                    System.IO.File.Copy(source, dTemp, true);

                                    try
                                    {
                                        System.IO.File.SetAttributes(d, System.IO.FileAttributes.Normal);
                                        System.IO.File.Copy(dTemp, d, true);
                                    }
                                    catch (Exception ex)
                                    {
                                        throw new System.IO.IOException("Destination file could not be overwritten: " + d, ex);
                                    }

                                    break;

                                case FileExistsAction.MakeUnique:
                                    d = File.UniqueFilename(destination);

                                    System.IO.File.Copy(source, dTemp, true);
                                    System.IO.File.Move(dTemp, d);
                                    break;
                                }
                            }
                            else
                            {
                                System.IO.File.Copy(source, dTemp, true);
                                System.IO.File.Move(dTemp, d);
                            }

                            destinationFilename = d;

                            if (isMove)
                            {
                                while (retryCount >= 0)
                                {
                                    try
                                    {
                                        System.IO.File.Delete(source);
                                        break;
                                    }
                                    catch
                                    {
                                        if (retryCount <= 0)
                                        {
                                            throw;
                                        }
                                    }

                                    if (retryCount > 0)
                                    {
                                        System.Threading.Thread.Sleep(retryDelaySeconds * 1000);
                                    }

                                    retryCount--;
                                }
                            }
                        }
                        finally
                        {
                            int retry = 3;
                            while (retry-- > 0)
                            {
                                try
                                {
                                    if (System.IO.File.Exists(dTemp))
                                    {
                                        System.IO.File.Delete(dTemp);
                                    }

                                    break;
                                }
                                catch
                                {
                                    if (retry > 0)
                                    {
                                        System.Threading.Thread.Sleep(1000);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        if (System.IO.File.Exists(d))
                        {
                            switch (ifExists)
                            {
                            case FileExistsAction.Throw:
                                throw new System.IO.IOException("Destination file already exists: " + destination);

                            case FileExistsAction.Skip:
                                return;

                            case FileExistsAction.Overwrite:

                                System.IO.File.SetAttributes(d, System.IO.FileAttributes.Normal);

                                System.IO.File.Copy(source, d, true);

                                if (isMove)
                                {
                                    while (retryCount >= 0)
                                    {
                                        try
                                        {
                                            System.IO.File.Delete(source);
                                            break;
                                        }
                                        catch
                                        {
                                            if (retryCount <= 0)
                                            {
                                                throw;
                                            }
                                        }

                                        if (retryCount > 0)
                                        {
                                            System.Threading.Thread.Sleep(retryDelaySeconds * 1000);
                                        }

                                        retryCount--;
                                    }
                                }

                                break;



                            case FileExistsAction.OverwriteIfNewer:

                                if (System.IO.File.GetLastWriteTimeUtc(d) >= System.IO.File.GetLastWriteTimeUtc(source))
                                {
                                    return;
                                }

                                System.IO.File.SetAttributes(d, System.IO.FileAttributes.Normal);

                                System.IO.File.Copy(source, d, true);

                                if (isMove)
                                {
                                    while (retryCount >= 0)
                                    {
                                        try
                                        {
                                            System.IO.File.Delete(source);
                                            break;
                                        }
                                        catch
                                        {
                                            if (retryCount <= 0)
                                            {
                                                throw;
                                            }
                                        }

                                        if (retryCount > 0)
                                        {
                                            System.Threading.Thread.Sleep(retryDelaySeconds * 1000);
                                        }

                                        retryCount--;
                                    }
                                }

                                break;

                            case FileExistsAction.MakeUnique:

                                d = File.UniqueFilename(destination);

                                if (isMove)
                                {
                                    System.IO.File.Move(source, d);
                                }
                                else
                                {
                                    System.IO.File.Copy(source, d);
                                }

                                break;
                            }
                        }
                        else
                        {
                            if (isMove)
                            {
                                System.IO.File.Move(source, d);
                            }
                            else
                            {
                                System.IO.File.Copy(source, d);
                            }
                        }

                        destinationFilename = d;
                    }

                    if (destinationFilename == d)
                    {
                        break;
                    }
                }
                catch
                {
                    if (retryCount <= 0)
                    {
                        throw;
                    }
                }

                if (retryCount > 0)
                {
                    System.Threading.Thread.Sleep(retryDelaySeconds * 1000);
                }

                retryCount--;
            }

            if (String.IsNullOrEmpty(destinationFilename))
            {
                if (isMove)
                {
                    throw new Exception("File move failed: " + source);
                }
                else
                {
                    throw new Exception("File copy failed: " + source);
                }
            }
        }