示例#1
0
        public void TestFindChanges()
        {
            var directory = Path.GetDirectoryName(typeof(ApiDiffTest).Assembly.Location);

            var module1 = CecilUtility.ReadModule(Path.Combine(directory, "TestLibrary.V1.dll"));
            var module2 = CecilUtility.ReadModule(Path.Combine(directory, "TestLibrary.V2.dll"));

            FacadeModuleProcessor.MakePublicFacade(module1, false);
            FacadeModuleProcessor.MakePublicFacade(module2, false);

            var changes = ApiDiff.FindChanges(module1, module2);

            var diff         = NormalizeDiff(changes.Select(change => $"{(change.IsBreaking ? "B" : "N")} {change.Message}"));
            var expectedDiff = NormalizeDiff(File.ReadAllLines(Path.Join(directory, "expected-diff.txt")));

            var falseNegatives = diff.Except(expectedDiff).ToList();

            if (falseNegatives.Count != 0)
            {
                Console.WriteLine("false positives:");
                Console.Write(string.Join(Environment.NewLine, falseNegatives));
                Console.WriteLine();
            }
            var falsePositives = expectedDiff.Except(diff).ToList();

            if (falsePositives.Count != 0)
            {
                Console.WriteLine("false negatives:");
                Console.Write(string.Join(Environment.NewLine, falsePositives));
                Console.WriteLine();
            }

            CollectionAssert.AreEqual(expectedDiff, diff);
        }
 private void ReleasePackageValidateApiDiffs(ApiDiff diff, VersionChangeType changeType)
 {
     if (diff.breakingChanges > 0 && changeType != VersionChangeType.Major)
     {
         AddError("Breaking changes require a new major version.");
     }
     if (diff.additions > 0 && changeType == VersionChangeType.Patch)
     {
         AddError("Additions require a new minor or major version.");
     }
     if (changeType != VersionChangeType.Major)
     {
         foreach (var assembly in diff.missingAssemblies)
         {
             AddError("Assembly \"{0}\" no longer exists or is no longer included in build. This change requires a new major version.", assembly);
         }
     }
     if (changeType == VersionChangeType.Patch)
     {
         foreach (var assembly in diff.newAssemblies)
         {
             AddError("New assembly \"{0}\" may only be added in a new minor or major version.", assembly);
         }
     }
 }
        static IReadOnlyList <TypeChanges> FindChanges(Stream stream1, Stream stream2)
        {
            var module1 = CecilUtility.ReadModule(stream1);
            var module2 = CecilUtility.ReadModule(stream2);

            FacadeModuleProcessor.MakePublicFacade(module1, keepInternalTypes: false);
            FacadeModuleProcessor.MakePublicFacade(module2, keepInternalTypes: false);

            return(ApiDiff.FindTypeChanges(module1, module2));
        }
        private void ExperimentalPackageValidateApiDiffs(ApiDiff diff, VersionChangeType changeType)
        {
            if (diff.breakingChanges > 0 && changeType == VersionChangeType.Patch)
            {
                AddError("For Experimental or Preview Packages, breaking changes require a new minor version.");
            }

            if (changeType == VersionChangeType.Patch)
            {
                foreach (var assembly in diff.missingAssemblies)
                {
                    AddError("Assembly \"{0}\" no longer exists or is no longer included in build. For Experimental or Preview Packages, this change requires a new minor version.", assembly);
                }
            }
        }
示例#5
0
        protected override bool OnInvoke(IEnumerable <string> extras)
        {
            if (Program.Verbose)
            {
                Console.WriteLine($"Comparing the following assemblies:");
                foreach (var assembly in Assemblies)
                {
                    Console.WriteLine($" - {assembly}");
                }

                if (Directories.Count > 0)
                {
                    Console.WriteLine($"Using the following search diectories:");
                    foreach (var directory in Directories)
                    {
                        Console.WriteLine($" - {directory}");
                    }
                }
            }

            var infoStreams = new List <Stream>();

            {
                var config = new ApiInfoConfig
                {
                    IgnoreResolutionErrors = !alwaysResolve,
                    SearchDirectories      = Directories,
                };

                foreach (var assembly in Assemblies)
                {
                    if (Program.Verbose)
                    {
                        Console.WriteLine($"Generating API info for {assembly}...");
                    }

                    var xdoc = GetApiInfoDocument(assembly, config, out var wasXml);

                    if (!wasXml && outputApi)
                    {
                        xdoc.Save(assembly + ".api.xml");
                    }

                    // in order to compare assemblies, they have to be the same name
                    var xass = xdoc.Element("assemblies").Element("assembly");
                    xass.SetAttributeValue("name", "Assembly");

                    var stream = new MemoryStream();
                    xdoc.Save(stream);
                    stream.Position = 0;

                    infoStreams.Add(stream);
                }
            }

            GenerateDiff(xmlDiff, writer =>
            {
                if (Program.Verbose)
                {
                    Console.WriteLine($"Generating XML diff at {xmlDiff}...");
                }

                infoStreams[0].Position = 0;
                infoStreams[1].Position = 0;
                ApiDiff.Generate(infoStreams[0], infoStreams[1], writer);
            });
            GenerateDiff(htmlDiff, writer =>
            {
                if (Program.Verbose)
                {
                    Console.WriteLine($"Generating HTML diff at {htmlDiff}...");
                }

                infoStreams[0].Position = 0;
                infoStreams[1].Position = 0;
                var config = new ApiDiffFormattedConfig
                {
                    Colorize                   = true,
                    Formatter                  = ApiDiffFormatter.Html,
                    IgnoreNonbreaking          = excludeNonbreaking,
                    IgnoreParameterNameChanges = excludeParameterNames,
                };
                ApiDiffFormatted.Generate(infoStreams[0], infoStreams[1], writer, config);
            });
            GenerateDiff(mdDiff, writer =>
            {
                if (Program.Verbose)
                {
                    Console.WriteLine($"Generating Markdown diff at {mdDiff}...");
                }

                infoStreams[0].Position = 0;
                infoStreams[1].Position = 0;
                var config = new ApiDiffFormattedConfig
                {
                    Formatter                  = ApiDiffFormatter.Markdown,
                    IgnoreNonbreaking          = excludeNonbreaking,
                    IgnoreParameterNameChanges = excludeParameterNames,
                };
                ApiDiffFormatted.Generate(infoStreams[0], infoStreams[1], writer, config);
            });

            return(true);
        }
        private void CheckApiDiff(AssemblyInfo[] assemblyInfo)
        {
#if UNITY_2018_1_OR_NEWER && !UNITY_2019_1_OR_NEWER
            TestState = TestState.NotRun;
            AddInformation("Api breaking changes validation only available on Unity 2019.1 or newer.");
            return;
#else
            var diff = new ApiDiff();
            var assembliesForPackage = assemblyInfo.Where(a => !validationAssemblyInformation.IsTestAssembly(a)).ToArray();
            if (Context.PreviousPackageBinaryDirectory == null)
            {
                TestState = TestState.NotRun;
                AddInformation("Previous package binaries must be present on artifactory to do API diff.");
                return;
            }

            var oldAssemblyPaths = Directory.GetFiles(Context.PreviousPackageBinaryDirectory, "*.dll");

            //Build diff
            foreach (var info in assembliesForPackage)
            {
                var assemblyDefinition = info.assemblyDefinition;
                var oldAssemblyPath    = oldAssemblyPaths.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p) == assemblyDefinition.name);

                if (info.assembly != null)
                {
                    var extraSearchFolder    = Path.GetDirectoryName(typeof(System.ObsoleteAttribute).Assembly.Location);
                    var assemblySearchFolder = new[]
                    {
                        extraSearchFolder,                                                              // System assemblies folder
                        Path.Combine(EditorApplication.applicationContentsPath, "Managed"),             // Main Unity assemblies folder.
                        Path.Combine(EditorApplication.applicationContentsPath, "Managed/UnityEngine"), // Module assemblies folder.
                        Path.GetDirectoryName(info.assembly.outputPath),                                // TODO: This is not correct. We need to keep all dependencies for the previous binaries. For now, use the same folder as the current version when resolving dependencies.
                        Context.ProjectPackageInfo.path                                                 // make sure to add the package folder as well, because it may contain .dll files
                    };

                    const string logsDirectory = "Logs";
                    if (!Directory.Exists(logsDirectory))
                    {
                        Directory.CreateDirectory(logsDirectory);
                    }

                    File.WriteAllText($"{logsDirectory}/ApiValidationParameters.txt", $"previous: {oldAssemblyPath}\ncurrent: {info.assembly.outputPath}\nsearch path: {string.Join("\n", assemblySearchFolder)}");
                    var apiChangesAssemblyInfo = new APIChangesCollector.AssemblyInfo()
                    {
                        BaseAssemblyPath = oldAssemblyPath,
                        BaseAssemblyExtraSearchFolders = assemblySearchFolder,
                        CurrentAssemblyPath            = info.assembly.outputPath,
                        CurrentExtraSearchFolders      = assemblySearchFolder
                    };

                    List <IAPIChange> entityChanges;
                    try
                    {
                        entityChanges = APIChangesCollector.Collect(apiChangesAssemblyInfo)
                                        .SelectMany(c => c.Changes).ToList();
                    }
                    catch (AssemblyResolutionException exception)
                    {
                        if (exception.AssemblyReference.Name == "UnityEditor.CoreModule" ||
                            exception.AssemblyReference.Name == "UnityEngine.CoreModule")
                        {
                            AddError(
                                "Failed comparing against assemblies of previously promoted version of package. \n" +
                                "This is most likely because the assemblies that were compared against were built with a different version of Unity. \n" +
                                "If you are certain that there are no API changes warranting bumping the package version then you can add an exception for this error:\n" +
                                ErrorDocumentation.GetLinkMessage("validation_exceptions.html", ""));
                            AddInformation($"APIChangesCollector.Collect threw exception:\n{exception}");
                            return;
                        }

                        throw;
                    }

                    var assemblyChange = new AssemblyChange(info.assembly.name)
                    {
                        additions = entityChanges.Where(c => c.IsAdd()).Select(c => c.ToString()).ToList(),
                        // Among all attribute changes, only the Obsolete attribute should be considered a breaking change
                        breakingChanges = entityChanges.Where(c => !c.IsAdd() && !((c.GetType()).Equals(typeof(AttributeChange)))).Select(c => c.ToString()).ToList()
                    };

                    if (entityChanges.Count > 0)
                    {
                        diff.assemblyChanges.Add(assemblyChange);
                    }
                }

                if (oldAssemblyPath == null)
                {
                    diff.newAssemblies.Add(assemblyDefinition.name);
                }
            }

            foreach (var oldAssemblyPath in oldAssemblyPaths)
            {
                var oldAssemblyName = Path.GetFileNameWithoutExtension(oldAssemblyPath);
                if (assembliesForPackage.All(a => a.assemblyDefinition.name != oldAssemblyName))
                {
                    diff.missingAssemblies.Add(oldAssemblyName);
                }
            }

            //separate changes
            diff.additions            = diff.assemblyChanges.Sum(v => v.additions.Count);
            diff.removedAssemblyCount = diff.missingAssemblies.Count;
            diff.breakingChanges      = diff.assemblyChanges.Sum(v => v.breakingChanges.Count);

            AddInformation("Tested against version {0}", Context.PreviousPackageInfo.Id);
            AddInformation("API Diff - Breaking changes: {0} Additions: {1} Missing Assemblies: {2}",
                           diff.breakingChanges,
                           diff.additions,
                           diff.removedAssemblyCount);

            if (diff.breakingChanges > 0 || diff.additions > 0)
            {
                TestOutput.AddRange(diff.assemblyChanges.Select(c => new ValidationTestOutput()
                {
                    Type = TestOutputType.Information, Output = JsonUtility.ToJson(c, true)
                }));
            }

            string json = JsonUtility.ToJson(diff, true);
            Directory.CreateDirectory(ValidationSuiteReport.ResultsPath);
            File.WriteAllText(Path.Combine(ValidationSuiteReport.ResultsPath, "ApiValidationReport.json"), json);

            //Figure out type of version change (patch, minor, major)
            //Error if changes are not allowed
            var changeType = Context.VersionChangeType;

            if (changeType == VersionChangeType.Unknown)
            {
                return;
            }

            if (Context.ProjectPackageInfo.LifecyclePhase == LifecyclePhase.Preview || Context.ProjectPackageInfo.LifecyclePhase == LifecyclePhase.Experimental)
            {
                ExperimentalPackageValidateApiDiffs(diff, changeType);
            }
            else
            {
                ReleasePackageValidateApiDiffs(diff, changeType);
            }
#endif
        }