From 85fa4e736b4a42145f4789f52168dc63e1440f09 Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 17 Mar 2023 17:54:18 +0100
Subject: [PATCH 01/10] Add test for XmlDependencies.IsDependenciesFile

This commit records the current behaviour of the method before I make changes to try and optimize it.
---
 .../AndroidResolverTests.csproj               | 63 +++++++++++++++++++
 .../Properties/AssemblyInfo.cs                | 35 +++++++++++
 .../XmlDependenciesTests.cs                   | 29 +++++++++
 source/AndroidResolverTests/packages.config   |  4 ++
 source/ExternalDependencyManager.sln          |  6 ++
 5 files changed, 137 insertions(+)
 create mode 100644 source/AndroidResolverTests/AndroidResolverTests.csproj
 create mode 100644 source/AndroidResolverTests/Properties/AssemblyInfo.cs
 create mode 100644 source/AndroidResolverTests/XmlDependenciesTests.cs
 create mode 100644 source/AndroidResolverTests/packages.config

diff --git a/source/AndroidResolverTests/AndroidResolverTests.csproj b/source/AndroidResolverTests/AndroidResolverTests.csproj
new file mode 100644
index 00000000..84317b99
--- /dev/null
+++ b/source/AndroidResolverTests/AndroidResolverTests.csproj
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+	<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+	<PropertyGroup>
+		<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+		<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+		<ProjectGuid>{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}</ProjectGuid>
+		<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+		<OutputType>Library</OutputType>
+		<AppDesignerFolder>Properties</AppDesignerFolder>
+		<RootNamespace>AndroidResolverTests</RootNamespace>
+		<AssemblyName>AndroidResolverTests</AssemblyName>
+		<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+		<FileAlignment>512</FileAlignment>
+	</PropertyGroup>
+	<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+		<PlatformTarget>AnyCPU</PlatformTarget>
+		<DebugSymbols>true</DebugSymbols>
+		<DebugType>full</DebugType>
+		<Optimize>false</Optimize>
+		<OutputPath>bin\Debug\</OutputPath>
+		<DefineConstants>DEBUG;TRACE</DefineConstants>
+		<ErrorReport>prompt</ErrorReport>
+		<WarningLevel>4</WarningLevel>
+	</PropertyGroup>
+	<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+		<PlatformTarget>AnyCPU</PlatformTarget>
+		<DebugType>pdbonly</DebugType>
+		<Optimize>true</Optimize>
+		<OutputPath>bin\Release\</OutputPath>
+		<DefineConstants>TRACE</DefineConstants>
+		<ErrorReport>prompt</ErrorReport>
+		<WarningLevel>4</WarningLevel>
+	</PropertyGroup>
+	<ItemGroup>
+		<Reference Include="System" />
+		<Reference Include="System.Core" />
+		<Reference Include="System.Data" />
+		<Reference Include="System.Xml" />
+		<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb">
+			<HintPath>..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll</HintPath>
+		</Reference>
+	</ItemGroup>
+	<ItemGroup>
+		<Compile Include="XmlDependenciesTests.cs" />
+		<Compile Include="Properties\AssemblyInfo.cs" />
+	</ItemGroup>
+	<ItemGroup>
+	  <ProjectReference Include="..\AndroidResolver\AndroidResolver.csproj">
+	    <Project>{82eedfbe-afe4-4def-99d9-bc929747dd9a}</Project>
+	    <Name>AndroidResolver</Name>
+	  </ProjectReference>
+	</ItemGroup>
+	<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+	<!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+		 Other similar extension points exist, see Microsoft.Common.targets.
+	<Target Name="BeforeBuild">
+	</Target>
+	<Target Name="AfterBuild">
+	</Target>
+	-->
+
+</Project>
diff --git a/source/AndroidResolverTests/Properties/AssemblyInfo.cs b/source/AndroidResolverTests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..db9ba593
--- /dev/null
+++ b/source/AndroidResolverTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AndroidResolverTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AndroidResolverTests")]
+[assembly: AssemblyCopyright("Copyright ©  2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("12F40968-4B80-4DCF-8D51-4C8E06F9CC4B")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/source/AndroidResolverTests/XmlDependenciesTests.cs b/source/AndroidResolverTests/XmlDependenciesTests.cs
new file mode 100644
index 00000000..9495f1b4
--- /dev/null
+++ b/source/AndroidResolverTests/XmlDependenciesTests.cs
@@ -0,0 +1,29 @@
+using System.Text.RegularExpressions;
+
+namespace GooglePlayServices.Tests {
+	using System;
+	using NUnit.Framework;
+
+	[TestFixture]
+	public class XmlDependenciesTests
+	{
+		[TestCase("Assets/Editor/Dependencies.xml")]
+		[TestCase("Assets/Editor/MyFolder/Dependencies.xml")]
+		[TestCase("Editor/Dependencies.xml")]
+		[TestCase("Editor/MyFolder/Dependencies.xml")]
+		[TestCase("Assets/Editor/SomeDependencies.xml")]
+		[TestCase("Assets/MyEditorCode/Dependencies.xml")]
+		[TestCase("Assets/MyEditorCode/SomeDependencies.xml")]
+		[TestCase("Assets/Editor/")]
+		[TestCase("Assets/Editor/Dependendencies")]
+		public void Test1(string path) {
+			var dependencies = new XmlDependencies();
+			bool actualResult = dependencies.IsDependenciesFile(path);
+
+			// This logic was part of the previous unoptimized implementation and can act as a test reference.
+			bool expectedResult = Regex.IsMatch(input: path, pattern: @".*[/\\]Editor[/\\].*Dependencies\.xml$");
+
+			Assert.AreEqual(expectedResult, actualResult);
+		}
+	}
+}
\ No newline at end of file
diff --git a/source/AndroidResolverTests/packages.config b/source/AndroidResolverTests/packages.config
new file mode 100644
index 00000000..c108d442
--- /dev/null
+++ b/source/AndroidResolverTests/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="NUnit" version="3.5.0" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/source/ExternalDependencyManager.sln b/source/ExternalDependencyManager.sln
index 9dda1103..a695f644 100644
--- a/source/ExternalDependencyManager.sln
+++ b/source/ExternalDependencyManager.sln
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerClientIntegra
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageMigratorIntegrationTests", "PackageManagerResolver\test\PackageMigratorIntegrationTests.csproj", "{4DBDEE33-4B6C-A866-93FE-04C15486BB03}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolverTests", "AndroidResolverTests\AndroidResolverTests.csproj", "{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -85,5 +87,9 @@ Global
 		{4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.Build.0 = Release|Any CPU
+		{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal

From 8b3b7fbada715a655623c35789f1c5c70c988219 Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 17 Mar 2023 18:25:33 +0100
Subject: [PATCH 02/10] Improve XmlDependencies.IsDependenciesFile performance

This simpler string manipulation checks the same logic as before but much faster in the context of a large project. If the OnPostprocessAllAssets callback is invoked multiple times with thousands of files, as can happen with non-trivial Unity projects, the regex call becomes quite expensive.

For profiling data see: https://github.com/googlesamples/unity-jar-resolver/issues/601
---
 source/AndroidResolver/src/XmlDependencies.cs       | 10 +++-------
 source/AndroidResolverTests/XmlDependenciesTests.cs |  5 ++---
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/source/AndroidResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs
index 24a02594..7621e3b2 100644
--- a/source/AndroidResolver/src/XmlDependencies.cs
+++ b/source/AndroidResolver/src/XmlDependencies.cs
@@ -46,13 +46,9 @@ internal class XmlDependencies {
         /// </summary>
         /// <param name="filename"></param>
         /// <returns>true if it is a match, false otherwise.</returns>
-        internal bool IsDependenciesFile(string filename) {
-            foreach (var regex in fileRegularExpressions) {
-                if (regex.Match(filename).Success) {
-                    return true;
-                }
-            }
-            return false;
+        internal static bool IsDependenciesFile(string filename) {
+            bool isInEditorFolder = filename.Contains("/Editor/") || filename.Contains(@"\Editor\");
+            return isInEditorFolder && filename.EndsWith("Dependencies.xml");
         }
 
         /// <summary>
diff --git a/source/AndroidResolverTests/XmlDependenciesTests.cs b/source/AndroidResolverTests/XmlDependenciesTests.cs
index 9495f1b4..998e46cb 100644
--- a/source/AndroidResolverTests/XmlDependenciesTests.cs
+++ b/source/AndroidResolverTests/XmlDependenciesTests.cs
@@ -16,9 +16,8 @@ public class XmlDependenciesTests
 		[TestCase("Assets/MyEditorCode/SomeDependencies.xml")]
 		[TestCase("Assets/Editor/")]
 		[TestCase("Assets/Editor/Dependendencies")]
-		public void Test1(string path) {
-			var dependencies = new XmlDependencies();
-			bool actualResult = dependencies.IsDependenciesFile(path);
+		public void IsDependenciesFileReturnsExpected(string path) {
+			bool actualResult = XmlDependencies.IsDependenciesFile(path);
 
 			// This logic was part of the previous unoptimized implementation and can act as a test reference.
 			bool expectedResult = Regex.IsMatch(input: path, pattern: @".*[/\\]Editor[/\\].*Dependencies\.xml$");

From dcae0f9281d338d679b3466f6610dd14f0c90a82 Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 17 Mar 2023 18:48:59 +0100
Subject: [PATCH 03/10] Remove XmlDependencies.fileRegularExpressions

Internally, only Dependencies.xml files are matched, so we can use the more performant file-matching pattern here.

The public API remains unchanged and only supports regex for now.
---
 source/AndroidResolver/AndroidResolver.csproj      |  1 +
 source/AndroidResolver/src/FileMatchPattern.cs     |  3 +++
 source/AndroidResolver/src/PlayServicesResolver.cs | 10 ++++++----
 source/AndroidResolver/src/XmlDependencies.cs      |  8 --------
 source/IOSResolver/src/IOSResolver.cs              |  2 +-
 5 files changed, 11 insertions(+), 13 deletions(-)
 create mode 100644 source/AndroidResolver/src/FileMatchPattern.cs

diff --git a/source/AndroidResolver/AndroidResolver.csproj b/source/AndroidResolver/AndroidResolver.csproj
index 08059780..d870c650 100644
--- a/source/AndroidResolver/AndroidResolver.csproj
+++ b/source/AndroidResolver/AndroidResolver.csproj
@@ -59,6 +59,7 @@
     <Compile Include="src\CommandLine.cs" />
     <Compile Include="src\CommandLineDialog.cs" />
     <Compile Include="src\EmbeddedResource.cs" />
+    <Compile Include="src\FileMatchPattern.cs" />
     <Compile Include="src\GradleResolver.cs" />
     <Compile Include="src\GradleTemplateResolver.cs" />
     <Compile Include="src\GradleWrapper.cs" />
diff --git a/source/AndroidResolver/src/FileMatchPattern.cs b/source/AndroidResolver/src/FileMatchPattern.cs
new file mode 100644
index 00000000..affc11ac
--- /dev/null
+++ b/source/AndroidResolver/src/FileMatchPattern.cs
@@ -0,0 +1,3 @@
+namespace GooglePlayServices {
+	public delegate bool FileMatchPattern(string filename);
+}
\ No newline at end of file
diff --git a/source/AndroidResolver/src/PlayServicesResolver.cs b/source/AndroidResolver/src/PlayServicesResolver.cs
index bbfb5ead..f693d0ee 100644
--- a/source/AndroidResolver/src/PlayServicesResolver.cs
+++ b/source/AndroidResolver/src/PlayServicesResolver.cs
@@ -18,6 +18,7 @@ namespace GooglePlayServices {
     using System;
     using System.Collections.Generic;
     using System.IO;
+    using System.Linq;
     using System.Text.RegularExpressions;
     using System.Threading;
     using System.Xml;
@@ -992,7 +993,7 @@ private static bool Initialize() {
             }
 
             // Monitor Android dependency XML files to perform auto-resolution.
-            AddAutoResolutionFilePatterns(xmlDependencies.fileRegularExpressions);
+            autoResolveFilePatterns.Add(XmlDependencies.IsDependenciesFile);
 
             svcSupport = PlayServicesSupport.CreateInstance(
                 "PlayServicesResolver",
@@ -1079,7 +1080,7 @@ internal static void Log(string message, Google.LogLevel level = LogLevel.Info)
         /// <summary>
         /// Patterns of files that are monitored to trigger auto resolution.
         /// </summary>
-        private static HashSet<Regex> autoResolveFilePatterns = new HashSet<Regex>();
+        private static HashSet<FileMatchPattern> autoResolveFilePatterns = new HashSet<FileMatchPattern>();
 
         /// <summary>
         /// Add file patterns to monitor to trigger auto resolution.
@@ -1087,7 +1088,8 @@ internal static void Log(string message, Google.LogLevel level = LogLevel.Info)
         /// <param name="patterns">Set of file patterns to monitor to trigger auto
         /// resolution.</param>
         public static void AddAutoResolutionFilePatterns(IEnumerable<Regex> patterns) {
-            autoResolveFilePatterns.UnionWith(patterns);
+            // Only regex patterns are supported in the public API, but a more performant default is used internally.
+            autoResolveFilePatterns.UnionWith(patterns.Select<Regex, FileMatchPattern>(p => p.IsMatch));
         }
 
         /// <summary>
@@ -1099,7 +1101,7 @@ private static bool CheckFilesForAutoResolution(HashSet<string> filesToCheck) {
             bool resolve = false;
             foreach (var asset in filesToCheck) {
                 foreach (var pattern in autoResolveFilePatterns) {
-                    if (pattern.Match(asset).Success) {
+                    if (pattern.Invoke(asset)) {
                         Log(String.Format("Found asset {0} matching {1}, attempting " +
                                           "auto-resolution.",
                                           asset, pattern.ToString()),
diff --git a/source/AndroidResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs
index 7621e3b2..9e6721f2 100644
--- a/source/AndroidResolver/src/XmlDependencies.cs
+++ b/source/AndroidResolver/src/XmlDependencies.cs
@@ -28,14 +28,6 @@ namespace GooglePlayServices {
     /// </summary>
     internal class XmlDependencies {
 
-        /// <summary>
-        /// Set of regular expressions that match files which contain dependency
-        /// specifications.
-        /// </summary>
-        internal HashSet<Regex> fileRegularExpressions = new HashSet<Regex> {
-            new Regex(@".*[/\\]Editor[/\\].*Dependencies\.xml$")
-        };
-
         /// <summary>
         /// Human readable name for dependency files managed by this class.
         /// </summary>
diff --git a/source/IOSResolver/src/IOSResolver.cs b/source/IOSResolver/src/IOSResolver.cs
index ab6c2cf1..d1311909 100644
--- a/source/IOSResolver/src/IOSResolver.cs
+++ b/source/IOSResolver/src/IOSResolver.cs
@@ -1939,7 +1939,7 @@ private static void OnPostprocessAllAssets(string[] importedAssets,
         var changedAssets = new List<string>(importedAssets);
         changedAssets.AddRange(deletedAssets);
         foreach (var asset in changedAssets) {
-            dependencyFileChanged = xmlDependencies.IsDependenciesFile(asset);
+            dependencyFileChanged = XmlDependencies.IsDependenciesFile(asset);
             if (dependencyFileChanged) break;
         }
         if (dependencyFileChanged) RefreshXmlDependencies();

From 84074efc30bb0d59963b6068d093c78a145c39d4 Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Thu, 8 Feb 2024 21:15:09 +0100
Subject: [PATCH 04/10] Move AndroidResolver tests into unit_tests directory

---
 .../unit_tests}/AndroidResolverTests.csproj                     | 2 +-
 .../unit_tests}/Properties/AssemblyInfo.cs                      | 0
 .../unit_tests}/XmlDependenciesTests.cs                         | 2 +-
 .../unit_tests}/packages.config                                 | 0
 source/ExternalDependencyManager.sln                            | 2 +-
 5 files changed, 3 insertions(+), 3 deletions(-)
 rename source/{AndroidResolverTests => AndroidResolver/unit_tests}/AndroidResolverTests.csproj (97%)
 rename source/{AndroidResolverTests => AndroidResolver/unit_tests}/Properties/AssemblyInfo.cs (100%)
 rename source/{AndroidResolverTests => AndroidResolver/unit_tests}/XmlDependenciesTests.cs (89%)
 rename source/{AndroidResolverTests => AndroidResolver/unit_tests}/packages.config (100%)

diff --git a/source/AndroidResolverTests/AndroidResolverTests.csproj b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
similarity index 97%
rename from source/AndroidResolverTests/AndroidResolverTests.csproj
rename to source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
index 84317b99..b1a17448 100644
--- a/source/AndroidResolverTests/AndroidResolverTests.csproj
+++ b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
@@ -46,7 +46,7 @@
 		<Compile Include="Properties\AssemblyInfo.cs" />
 	</ItemGroup>
 	<ItemGroup>
-	  <ProjectReference Include="..\AndroidResolver\AndroidResolver.csproj">
+	  <ProjectReference Include="..\AndroidResolver.csproj">
 	    <Project>{82eedfbe-afe4-4def-99d9-bc929747dd9a}</Project>
 	    <Name>AndroidResolver</Name>
 	  </ProjectReference>
diff --git a/source/AndroidResolverTests/Properties/AssemblyInfo.cs b/source/AndroidResolver/unit_tests/Properties/AssemblyInfo.cs
similarity index 100%
rename from source/AndroidResolverTests/Properties/AssemblyInfo.cs
rename to source/AndroidResolver/unit_tests/Properties/AssemblyInfo.cs
diff --git a/source/AndroidResolverTests/XmlDependenciesTests.cs b/source/AndroidResolver/unit_tests/XmlDependenciesTests.cs
similarity index 89%
rename from source/AndroidResolverTests/XmlDependenciesTests.cs
rename to source/AndroidResolver/unit_tests/XmlDependenciesTests.cs
index 998e46cb..f71d880e 100644
--- a/source/AndroidResolverTests/XmlDependenciesTests.cs
+++ b/source/AndroidResolver/unit_tests/XmlDependenciesTests.cs
@@ -19,7 +19,7 @@ public class XmlDependenciesTests
 		public void IsDependenciesFileReturnsExpected(string path) {
 			bool actualResult = XmlDependencies.IsDependenciesFile(path);
 
-			// This logic was part of the previous unoptimized implementation and can act as a test reference.
+			// This was the previous implementation before the optimization attempt and acts as a test reference.
 			bool expectedResult = Regex.IsMatch(input: path, pattern: @".*[/\\]Editor[/\\].*Dependencies\.xml$");
 
 			Assert.AreEqual(expectedResult, actualResult);
diff --git a/source/AndroidResolverTests/packages.config b/source/AndroidResolver/unit_tests/packages.config
similarity index 100%
rename from source/AndroidResolverTests/packages.config
rename to source/AndroidResolver/unit_tests/packages.config
diff --git a/source/ExternalDependencyManager.sln b/source/ExternalDependencyManager.sln
index a695f644..2702c081 100644
--- a/source/ExternalDependencyManager.sln
+++ b/source/ExternalDependencyManager.sln
@@ -27,7 +27,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerClientIntegra
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageMigratorIntegrationTests", "PackageManagerResolver\test\PackageMigratorIntegrationTests.csproj", "{4DBDEE33-4B6C-A866-93FE-04C15486BB03}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolverTests", "AndroidResolverTests\AndroidResolverTests.csproj", "{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolverTests", "AndroidResolver\unit_tests\AndroidResolverTests.csproj", "{12F40968-4B80-4DCF-8D51-4C8E06F9CC4B}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution

From 717d53d0ae7d2ac30545d121ba83b7a348af25cf Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Thu, 8 Feb 2024 21:17:29 +0100
Subject: [PATCH 05/10] Add TODO item to build.gradle to fix Nunit tests for
 #602

---
 build.gradle | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/build.gradle b/build.gradle
index 5c60b176..f45c065e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2246,6 +2246,8 @@ task gitTagRelease(type: Exec) {
 //   - Conflicting dependencies
 //   - FAT ABI vs. single ABI selection
 
+// TODO: Build and run NUnit tests for #602
+
 // TODO: iOS Resolver tests (on OSX only)
 // - Import plugin test Cocoapods bootstrap
 // - Pod specification, with reflection & XML, validate both are present in the

From 87f25bffa0719e3f49b36fe60fe9b225a226d85d Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 9 Feb 2024 09:08:41 +0100
Subject: [PATCH 06/10] Remove FileMatchPattern delegate and replace with
 System.Func

This was only added because I initially thought it would read better, but it also obscures the in and out types. Since there's no functional difference, I removed it again just to reduce the amount of custom code.
---
 source/AndroidResolver/AndroidResolver.csproj      | 1 -
 source/AndroidResolver/src/FileMatchPattern.cs     | 3 ---
 source/AndroidResolver/src/PlayServicesResolver.cs | 4 ++--
 3 files changed, 2 insertions(+), 6 deletions(-)
 delete mode 100644 source/AndroidResolver/src/FileMatchPattern.cs

diff --git a/source/AndroidResolver/AndroidResolver.csproj b/source/AndroidResolver/AndroidResolver.csproj
index d870c650..08059780 100644
--- a/source/AndroidResolver/AndroidResolver.csproj
+++ b/source/AndroidResolver/AndroidResolver.csproj
@@ -59,7 +59,6 @@
     <Compile Include="src\CommandLine.cs" />
     <Compile Include="src\CommandLineDialog.cs" />
     <Compile Include="src\EmbeddedResource.cs" />
-    <Compile Include="src\FileMatchPattern.cs" />
     <Compile Include="src\GradleResolver.cs" />
     <Compile Include="src\GradleTemplateResolver.cs" />
     <Compile Include="src\GradleWrapper.cs" />
diff --git a/source/AndroidResolver/src/FileMatchPattern.cs b/source/AndroidResolver/src/FileMatchPattern.cs
deleted file mode 100644
index affc11ac..00000000
--- a/source/AndroidResolver/src/FileMatchPattern.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace GooglePlayServices {
-	public delegate bool FileMatchPattern(string filename);
-}
\ No newline at end of file
diff --git a/source/AndroidResolver/src/PlayServicesResolver.cs b/source/AndroidResolver/src/PlayServicesResolver.cs
index f693d0ee..5353a4a1 100644
--- a/source/AndroidResolver/src/PlayServicesResolver.cs
+++ b/source/AndroidResolver/src/PlayServicesResolver.cs
@@ -1080,7 +1080,7 @@ internal static void Log(string message, Google.LogLevel level = LogLevel.Info)
         /// <summary>
         /// Patterns of files that are monitored to trigger auto resolution.
         /// </summary>
-        private static HashSet<FileMatchPattern> autoResolveFilePatterns = new HashSet<FileMatchPattern>();
+        private static HashSet<Func<string, bool>> autoResolveFilePatterns = new HashSet<Func<string, bool>>();
 
         /// <summary>
         /// Add file patterns to monitor to trigger auto resolution.
@@ -1089,7 +1089,7 @@ internal static void Log(string message, Google.LogLevel level = LogLevel.Info)
         /// resolution.</param>
         public static void AddAutoResolutionFilePatterns(IEnumerable<Regex> patterns) {
             // Only regex patterns are supported in the public API, but a more performant default is used internally.
-            autoResolveFilePatterns.UnionWith(patterns.Select<Regex, FileMatchPattern>(p => p.IsMatch));
+            autoResolveFilePatterns.UnionWith(patterns.Select<Regex, Func<string, bool>>(p => p.IsMatch));
         }
 
         /// <summary>

From 22d1a09c9615a054945664c77c9d54c12e8382df Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 9 Feb 2024 09:26:02 +0100
Subject: [PATCH 07/10] Refactor IsDependenciesFile

---
 source/AndroidResolver/src/XmlDependencies.cs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/source/AndroidResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs
index 9e6721f2..3b7df84e 100644
--- a/source/AndroidResolver/src/XmlDependencies.cs
+++ b/source/AndroidResolver/src/XmlDependencies.cs
@@ -36,11 +36,13 @@ internal class XmlDependencies {
         /// <summary>
         /// Determines whether a filename matches an XML dependencies file.
         /// </summary>
-        /// <param name="filename"></param>
         /// <returns>true if it is a match, false otherwise.</returns>
         internal static bool IsDependenciesFile(string filename) {
-            bool isInEditorFolder = filename.Contains("/Editor/") || filename.Contains(@"\Editor\");
-            return isInEditorFolder && filename.EndsWith("Dependencies.xml");
+            if (!filename.EndsWith("Dependencies.xml")) {
+                return false;
+            }
+
+            return filename.Contains("/Editor/") || filename.Contains(@"\Editor\");
         }
 
         /// <summary>

From 167a3ce68fbdea75e5ef1e3bb5d83c2d316593ea Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 9 Feb 2024 09:26:56 +0100
Subject: [PATCH 08/10] Use NUnit 2.6.3 instead of 3.5.0 in
 AndroidResolverTests because the earlier one already exists in the project

---
 source/AndroidResolver/unit_tests/AndroidResolverTests.csproj | 4 +---
 source/AndroidResolver/unit_tests/packages.config             | 2 +-
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
index b1a17448..519720d9 100644
--- a/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
+++ b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
@@ -37,9 +37,7 @@
 		<Reference Include="System.Core" />
 		<Reference Include="System.Data" />
 		<Reference Include="System.Xml" />
-		<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb">
-			<HintPath>..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll</HintPath>
-		</Reference>
+		<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb" />
 	</ItemGroup>
 	<ItemGroup>
 		<Compile Include="XmlDependenciesTests.cs" />
diff --git a/source/AndroidResolver/unit_tests/packages.config b/source/AndroidResolver/unit_tests/packages.config
index c108d442..ad37a528 100644
--- a/source/AndroidResolver/unit_tests/packages.config
+++ b/source/AndroidResolver/unit_tests/packages.config
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="NUnit" version="3.5.0" targetFramework="net45" />
+  <package id="NUnit" version="2.6.3" targetFramework="net45" />
 </packages>
\ No newline at end of file

From 62b458306848d427e36f590e9acb817c65312a16 Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 9 Feb 2024 09:47:44 +0100
Subject: [PATCH 09/10] Rename AndroidResolverTests assembly to
 Google.AndroidResolverTests to align with others

---
 source/AndroidResolver/unit_tests/AndroidResolverTests.csproj | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
index 519720d9..d4df5848 100644
--- a/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
+++ b/source/AndroidResolver/unit_tests/AndroidResolverTests.csproj
@@ -8,8 +8,8 @@
 		<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
 		<OutputType>Library</OutputType>
 		<AppDesignerFolder>Properties</AppDesignerFolder>
-		<RootNamespace>AndroidResolverTests</RootNamespace>
-		<AssemblyName>AndroidResolverTests</AssemblyName>
+		<RootNamespace>Google</RootNamespace>
+		<AssemblyName>Google.AndroidResolverTests</AssemblyName>
 		<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
 		<FileAlignment>512</FileAlignment>
 	</PropertyGroup>

From 2bad7a821132058e30d43573e0b292495f70939b Mon Sep 17 00:00:00 2001
From: Christopher Yarbrough <chris.yarbrough@innogames.com>
Date: Fri, 9 Feb 2024 09:56:28 +0100
Subject: [PATCH 10/10] Add comment to IsDependenciesFile

---
 source/AndroidResolver/src/XmlDependencies.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/AndroidResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs
index 3b7df84e..d0ea5bf5 100644
--- a/source/AndroidResolver/src/XmlDependencies.cs
+++ b/source/AndroidResolver/src/XmlDependencies.cs
@@ -41,7 +41,7 @@ internal static bool IsDependenciesFile(string filename) {
             if (!filename.EndsWith("Dependencies.xml")) {
                 return false;
             }
-
+            // This method was optimized to avoid using regex after profiling results from a large Unity project.
             return filename.Contains("/Editor/") || filename.Contains(@"\Editor\");
         }