Exemple #1
0
        /// <inheritdoc />
        public async Task <ToolResult> GetDifferences(string mapPathA, string mapPathB, CancellationToken cancellationToken)
        {
            if (mapPathA == null)
            {
                throw new ArgumentNullException(nameof(mapPathA));
            }
            if (mapPathB == null)
            {
                throw new ArgumentNullException(nameof(mapPathB));
            }

            var        output      = new StringBuilder();
            var        errorOutput = new StringBuilder();
            var        args        = String.Format(CultureInfo.InvariantCulture, "{2}diff-maps \"{0}\" \"{1}\"", mapPathA, mapPathB, dmeArgument);
            Task <int> processTask;

            using (var P = CreateDMMToolsProcess(output, errorOutput))
            {
                P.StartInfo.Arguments = args;

                processTask = StartAndWaitForProcessExit(P, cancellationToken);
                await processTask.ConfigureAwait(false);
            }
            var toolOutput = String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}{1}StdOut:{1}{2}{1}StdErr:{1}{3}", processTask.Result, Environment.NewLine, output, errorOutput);

            var result = new ToolResult
            {
                CommandLine = args,
                ToolOutput  = toolOutput
            };

            var matches = Regex.Matches(output.ToString(), "\\(([1-9][0-9]*), ([1-9][0-9]*), ([1-9][0-9]*)\\)");

            if (matches.Count == 0)
            {
                return(result);
            }

            var region = new MapRegion()
            {
                MinX = Int16.MaxValue,
                MinY = Int16.MaxValue
            };

            try
            {
                foreach (Match I in matches)
                {
                    region.MaxX = Math.Max(region.MaxX, Convert.ToInt16(I.Groups[1].Value, CultureInfo.InvariantCulture));
                    region.MinX = Math.Min(region.MinX, Convert.ToInt16(I.Groups[1].Value, CultureInfo.InvariantCulture));

                    region.MaxY = Math.Max(region.MaxY, Convert.ToInt16(I.Groups[2].Value, CultureInfo.InvariantCulture));
                    region.MinY = Math.Min(region.MinY, Convert.ToInt16(I.Groups[2].Value, CultureInfo.InvariantCulture));
                }
                result.MapRegion = region;
            }
            catch { }

            return(result);
        }
Exemple #2
0
        /// <inheritdoc />
        public async Task <ToolResult> GetMapSize(string mapPath, CancellationToken cancellationToken)
        {
            if (mapPath == null)
            {
                throw new ArgumentNullException(nameof(mapPath));
            }

            string     mapName;
            var        output      = new StringBuilder();
            var        errorOutput = new StringBuilder();
            var        args        = String.Format(CultureInfo.InvariantCulture, "{0}map-info -j \"{1}\"", dmeArgument, mapPath);
            Task <int> processTask;

            using (var P = CreateDMMToolsProcess(output, errorOutput))
            {
                P.StartInfo.Arguments = args;

                processTask = StartAndWaitForProcessExit(P, cancellationToken);

                mapName = Path.GetFileNameWithoutExtension(mapPath);

                await processTask.ConfigureAwait(false);
            }

            var toolOutput = String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}{1}StdOut:{1}{2}{1}StdErr:{1}{3}", processTask.Result, Environment.NewLine, output, errorOutput);
            var result     = new ToolResult {
                ToolOutput = toolOutput, CommandLine = args
            };

            try
            {
                var json = JsonConvert.DeserializeObject <IDictionary <string, IDictionary <string, object> > >(output.ToString());
                var map  = json[mapPath];
                var size = (JArray)map["size"];
                result.MapRegion = new MapRegion
                {
                    MinX = 1,
                    MinY = 1,
                    MaxX = (short)size[0],
                    MaxY = (short)size[1]
                };
            }
            catch { }
            return(result);
        }
Exemple #3
0
        /// <summary>
        /// Generate map diffs for a given <paramref name="pullRequest"/>
        /// </summary>
        /// <param name="pullRequest">The <see cref="PullRequest"/></param>
        /// <param name="checkRunId">The <see cref="CheckRun.Id"/></param>
        /// <param name="changedDmms">Paths to changed .dmm files</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task GenerateDiffs(PullRequest pullRequest, long checkRunId, IReadOnlyList <string> changedDmms, CancellationToken cancellationToken)
        {
            using (logger.BeginScope("Generating {0} diffs for pull request #{1} in {2}/{3}", changedDmms.Count, pullRequest.Number, pullRequest.Base.Repository.Owner.Login, pullRequest.Base.Repository.Name))
            {
                const string OldMapExtension = ".old_map_diff_bot";

                var gitHubManager = serviceProvider.GetRequiredService <IGitHubManager>();

                Task generatingCommentTask;
                List <Task <RenderResult> > afterRenderings, beforeRenderings;

                var workingDir = ioManager.ConcatPath(pullRequest.Base.Repository.Owner.Login, pullRequest.Base.Repository.Name, pullRequest.Number.ToString(CultureInfo.InvariantCulture));
                logger.LogTrace("Setting workdir to {0}", workingDir);
                IIOManager currentIOManager = new ResolvingIOManager(ioManager, workingDir);
                string     repoPath;

                int  lastProgress       = -1;
                Task lastProgressUpdate = Task.CompletedTask;
                async Task OnCloneProgress(int progress)
                {
                    lock (gitHubManager)
                    {
                        if (lastProgress >= progress)
                        {
                            return;
                        }
                        if (lastProgress == -1)
                        {
                            logger.LogInformation("Waiting on repository to finish cloning...");
                        }
                        lastProgress = progress;
                    }
                    await lastProgressUpdate.ConfigureAwait(false);

                    await gitHubManager.UpdateCheckRun(pullRequest.Base.Repository.Id, checkRunId, new CheckRunUpdate
                    {
                        Status = CheckStatus.InProgress,
                        Output = new CheckRunOutput(stringLocalizer["Cloning Repository"], stringLocalizer["Clone Progress: {0}%", progress], null, null, null),
                    }, cancellationToken).ConfigureAwait(false);
                };
                Task CreateBlockedComment()
                {
                    logger.LogInformation("Waiting for another diff generation on {0}/{1} to complete...", pullRequest.Base.Repository.Owner.Login, pullRequest.Base.Repository.Name);
                    return(gitHubManager.CreateSingletonComment(pullRequest, stringLocalizer["Waiting for another operation on this repository to complete..."], cancellationToken));
                };

                logger.LogTrace("Locking repository...");
                using (var repo = await repositoryManager.GetRepository(pullRequest.Base.Repository, OnCloneProgress, CreateBlockedComment, cancellationToken).ConfigureAwait(false))
                {
                    logger.LogTrace("Repository ready");
                    generatingCommentTask = gitHubManager.UpdateCheckRun(pullRequest.Base.Repository.Id, checkRunId, new CheckRunUpdate
                    {
                        Status = CheckStatus.InProgress,
                        Output = new CheckRunOutput(stringLocalizer["Generating Diffs"], stringLocalizer["Aww geez rick, I should eventually put some progress message here"], null, null, null),
                    }, cancellationToken);
                    //prep the outputDirectory
                    async Task DirectoryPrep()
                    {
                        logger.LogTrace("Cleaning workdir...");
                        await currentIOManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false);

                        await currentIOManager.CreateDirectory(".", cancellationToken).ConfigureAwait(false);

                        logger.LogTrace("Workdir cleaned");
                    };

                    var dirPrepTask = DirectoryPrep();
                    //get the dme to use
                    var dmeToUseTask = serviceProvider.GetRequiredService <IDatabaseContext>().InstallationRepositories.Where(x => x.Id == pullRequest.Base.Repository.Id).Select(x => x.TargetDme).ToAsyncEnumerable().FirstOrDefault(cancellationToken);

                    var oldMapPaths = new List <string>()
                    {
                        Capacity = changedDmms.Count
                    };
                    try
                    {
                        //fetch base commit if necessary and check it out, fetch pull request
                        if (!await repo.ContainsCommit(pullRequest.Base.Sha, cancellationToken).ConfigureAwait(false))
                        {
                            logger.LogTrace("Base commit not found, running fetch...");
                            await repo.Fetch(cancellationToken).ConfigureAwait(false);
                        }
                        logger.LogTrace("Moving HEAD to pull request base...");
                        await repo.Checkout(pullRequest.Base.Sha, cancellationToken).ConfigureAwait(false);

                        //but since we don't need this right await don't await it yet
                        var pullRequestFetchTask = repo.FetchPullRequest(pullRequest.Number, cancellationToken);
                        try
                        {
                            //first copy all modified maps to the same location with the .old_map_diff_bot extension
                            async Task <string> CacheMap(string mapPath)
                            {
                                var originalPath = currentIOManager.ConcatPath(repoPath, mapPath);

                                if (await currentIOManager.FileExists(originalPath, cancellationToken).ConfigureAwait(false))
                                {
                                    logger.LogTrace("Creating old map cache of {0}", mapPath);
                                    var oldMapPath = String.Format(CultureInfo.InvariantCulture, "{0}{1}", originalPath, OldMapExtension);
                                    await currentIOManager.CopyFile(originalPath, oldMapPath, cancellationToken).ConfigureAwait(false);

                                    return(oldMapPath);
                                }
                                return(null);
                            };

                            repoPath = repo.Path;

                            var tasks = changedDmms.Select(x => CacheMap(x)).ToList();
                            await Task.WhenAll(tasks).ConfigureAwait(false);

                            oldMapPaths.AddRange(tasks.Select(x => x.Result));
                        }
                        finally
                        {
                            logger.LogTrace("Waiting for pull request commits to be available...");
                            await pullRequestFetchTask.ConfigureAwait(false);
                        }

                        logger.LogTrace("Creating and moving HEAD to pull request merge commit...");
                        //generate the merge commit ourselves since we can't get it from GitHub because itll return an outdated one
                        await repo.Merge(pullRequest.Head.Sha, cancellationToken).ConfigureAwait(false);
                    }
                    finally
                    {
                        logger.LogTrace("Waiting for configured project dme...");
                        await dmeToUseTask.ConfigureAwait(false);
                    }

                    //create empty array of map regions
                    var mapRegions = Enumerable.Repeat <MapRegion>(null, changedDmms.Count).ToList();
                    var dmeToUse   = dmeToUseTask.Result;

                    var generator       = generatorFactory.CreateGenerator(dmeToUse, new ResolvingIOManager(ioManager, repoPath));
                    var outputDirectory = currentIOManager.ResolvePath(".");
                    logger.LogTrace("Full workdir path: {0}", outputDirectory);
                    //Generate MapRegions for modified maps and render all new maps
                    async Task <RenderResult> DiffAndRenderNewMap(int I)
                    {
                        await dirPrepTask.ConfigureAwait(false);

                        var originalPath = currentIOManager.ConcatPath(repoPath, changedDmms[I]);

                        if (!await currentIOManager.FileExists(originalPath, cancellationToken).ConfigureAwait(false))
                        {
                            logger.LogTrace("No new map for path {0} exists, skipping region detection and after render", changedDmms[I]);
                            return(new RenderResult {
                                InputPath = changedDmms[I], ToolOutput = stringLocalizer["Map missing!"]
                            });
                        }
                        ToolResult result = null;

                        if (oldMapPaths[I] != null)
                        {
                            logger.LogTrace("Getting diff region for {0}...", changedDmms[I]);
                            result = await generator.GetDifferences(oldMapPaths[I], originalPath, cancellationToken).ConfigureAwait(false);

                            var region = result.MapRegion;
                            logger.LogTrace("Diff region for {0}: {1}", changedDmms[I], region);
                            if (region != null)
                            {
                                var       xdiam             = region.MaxX - region.MinX;
                                var       ydiam             = region.MaxY - region.MinY;
                                const int minDiffDimensions = 5 - 1;
                                if (xdiam < minDiffDimensions || ydiam < minDiffDimensions)
                                {
                                    //need to expand
                                    var fullResult = await generator.GetMapSize(originalPath, cancellationToken).ConfigureAwait(false);

                                    var fullRegion = fullResult.MapRegion;
                                    if (fullRegion == null)
                                    {
                                        //give up
                                        region = null;
                                    }
                                    else
                                    {
                                        bool increaseMax = true;
                                        if (xdiam < minDiffDimensions && ((fullRegion.MaxX - fullRegion.MinX) >= minDiffDimensions))
                                        {
                                            while ((region.MaxX - region.MinX) < minDiffDimensions)
                                            {
                                                if (increaseMax)
                                                {
                                                    region.MaxX = (short)Math.Min(region.MaxX + 1, fullRegion.MaxX);
                                                }
                                                else
                                                {
                                                    region.MinX = (short)Math.Max(region.MinX - 1, 1);
                                                }
                                                increaseMax = !increaseMax;
                                            }
                                        }
                                        if (ydiam < minDiffDimensions && ((fullRegion.MaxY - fullRegion.MinY) >= minDiffDimensions))
                                        {
                                            while ((region.MaxY - region.MinY) < minDiffDimensions)
                                            {
                                                if (increaseMax)
                                                {
                                                    region.MaxY = (short)Math.Min(region.MaxY + 1, fullRegion.MaxY);
                                                }
                                                else
                                                {
                                                    region.MinY = (short)Math.Max(region.MinY - 1, 1);
                                                }
                                                increaseMax = !increaseMax;
                                            }
                                        }
                                    }
                                    logger.LogTrace("Region for {0} expanded to {1}", changedDmms[I], region);
                                }
                                mapRegions[I] = region;
                            }
                        }
                        else
                        {
                            logger.LogTrace("Skipping region detection for {0} due to old map not existing", changedDmms[I]);
                        }
                        logger.LogTrace("Performing after rendering for {0}...", changedDmms[I]);
                        var renderResult = await generator.RenderMap(originalPath, mapRegions[I], outputDirectory, "after", cancellationToken).ConfigureAwait(false);

                        logger.LogTrace("After rendering for {0} complete! Result path: {1}, Output: {2}", changedDmms[I], renderResult.OutputPath, renderResult.ToolOutput);
                        if (result != null)
                        {
                            renderResult.ToolOutput = String.Format(CultureInfo.InvariantCulture, "Differences task:{0}{1}{0}Render task:{0}{2}", Environment.NewLine, result.ToolOutput, renderResult.ToolOutput);
                        }
                        return(renderResult);
                    };

                    logger.LogTrace("Running iterations of DiffAndRenderNewMap...");
                    //finish up before we go back to the base branch
                    afterRenderings = Enumerable.Range(0, changedDmms.Count).Select(I => DiffAndRenderNewMap(I)).ToList();
                    try
                    {
                        await Task.WhenAll(afterRenderings).ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        logger.LogDebug(e, "After renderings produced exception!");
                        //at this point everything is done but some have failed
                        //we'll handle it later
                    }

                    logger.LogTrace("Moving HEAD back to pull request base...");
                    await repo.Checkout(pullRequest.Base.Sha, cancellationToken).ConfigureAwait(false);

                    Task <RenderResult> RenderOldMap(int i)
                    {
                        var oldPath = oldMapPaths[i];

                        if (oldMapPaths != null)
                        {
                            logger.LogTrace("Performing before rendering for {0}...", changedDmms[i]);
                            return(generator.RenderMap(oldPath, mapRegions[i], outputDirectory, "before", cancellationToken));
                        }
                        return(Task.FromResult(new RenderResult {
                            InputPath = changedDmms[i], ToolOutput = stringLocalizer["Map missing!"]
                        }));
                    }

                    logger.LogTrace("Running iterations of RenderOldMap...");
                    //finish up rendering
                    beforeRenderings = Enumerable.Range(0, changedDmms.Count).Select(I => RenderOldMap(I)).ToList();
                    try
                    {
                        await Task.WhenAll(beforeRenderings).ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        logger.LogDebug(e, "Before renderings produced exception!");
                        //see above
                    }

                    //done with the repo at this point
                    logger.LogTrace("Renderings complete. Releasing reposiotory");
                }

                //collect results and errors
                async Task <KeyValuePair <MapDiff, MapRegion> > GetResult(int i)
                {
                    var beforeTask = beforeRenderings[i];
                    var afterTask  = afterRenderings[i];

                    var result = new MapDiff
                    {
                        InstallationRepositoryId = pullRequest.Base.Repository.Id,
                        CheckRunId = checkRunId,
                        FileId     = i,
                    };

                    RenderResult GetRenderingResult(Task <RenderResult> task)
                    {
                        if (task.Exception != null)
                        {
                            result.LogMessage = result.LogMessage == null?task.Exception.ToString() : String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", result.LogMessage, Environment.NewLine, task.Exception);

                            return(null);
                        }
                        return(task.Result);
                    };

                    var r1 = GetRenderingResult(beforeTask);
                    var r2 = GetRenderingResult(afterTask);

                    logger.LogTrace("Results for {0}: Before {1}, After {2}", changedDmms[i], r1?.OutputPath ?? "NONE", r2?.OutputPath ?? "NONE");

                    result.MapPath = changedDmms[i];

                    result.LogMessage = String.Format(CultureInfo.InvariantCulture, "Job {5}:{0}Path: {6}{0}Before:{0}Command Line: {1}{0}Output:{0}{2}{0}Logs:{0}{7}{0}After:{0}Command Line: {3}{0}Output:{0}{4}{0}Logs:{0}{8}{0}Exceptions:{0}{9}{0}", Environment.NewLine, r1?.CommandLine, r1?.OutputPath, r2?.CommandLine, r2?.OutputPath, i + 1, result.MapPath, r1?.ToolOutput, r2?.ToolOutput, result.LogMessage);

                    async Task <byte[]> ReadMapImage(string path)
                    {
                        if (path != null && await currentIOManager.FileExists(path, cancellationToken).ConfigureAwait(false))
                        {
                            var bytes = await currentIOManager.ReadAllBytes(path, cancellationToken).ConfigureAwait(false);

                            await currentIOManager.DeleteFile(path, cancellationToken).ConfigureAwait(false);

                            return(bytes);
                        }
                        return(null);
                    }

                    var readBeforeTask = ReadMapImage(r1?.OutputPath);

                    result.AfterImage = await ReadMapImage(r2?.OutputPath).ConfigureAwait(false);

                    result.BeforeImage = await readBeforeTask.ConfigureAwait(false);

                    return(new KeyValuePair <MapDiff, MapRegion>(result, r2?.MapRegion));
                }

                logger.LogTrace("Waiting for notification comment to POST...");
                await generatingCommentTask.ConfigureAwait(false);

                logger.LogTrace("Collecting results...");
                var results = Enumerable.Range(0, changedDmms.Count).Select(x => GetResult(x)).ToList();
                await Task.WhenAll(results).ConfigureAwait(false);

                var dic = new Dictionary <MapDiff, MapRegion>();
                foreach (var I in results.Select(x => x.Result))
                {
                    dic.Add(I.Key, I.Value);
                }
                await HandleResults(pullRequest, checkRunId, dic, cancellationToken).ConfigureAwait(false);
            }
        }