-
Notifications
You must be signed in to change notification settings - Fork 62
Test work and initial BuildReport test #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+350
−64
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3b65f28
Establish test for BuildReport support
SkowronskiAndrew 31b43a5
Refactor new tests to make each query self contained and easier to ad…
SkowronskiAndrew cc7dbe9
Refactor to reduce repeated sql connection syntax
SkowronskiAndrew 92610eb
Extend the BuildReport tests to cover all the data currently tracked …
SkowronskiAndrew File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| using Microsoft.Data.Sqlite; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using NUnit.Framework; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace UnityDataTools.UnityDataTool.Tests; | ||
|
|
||
| #pragma warning disable NUnit2005, NUnit2006 | ||
|
|
||
| public class BuildReportTests | ||
| { | ||
| private string m_TestOutputFolder; | ||
| private string m_TestDataFolder; | ||
|
|
||
| [OneTimeSetUp] | ||
| public void OneTimeSetup() | ||
| { | ||
| m_TestOutputFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "test_folder"); | ||
| m_TestDataFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data"); | ||
| Directory.CreateDirectory(m_TestOutputFolder); | ||
| Directory.SetCurrentDirectory(m_TestOutputFolder); | ||
| } | ||
|
|
||
| [TearDown] | ||
| public void Teardown() | ||
| { | ||
| SqliteConnection.ClearAllPools(); | ||
|
|
||
| var testDir = new DirectoryInfo(m_TestOutputFolder); | ||
| testDir.EnumerateFiles() | ||
| .ToList().ForEach(f => f.Delete()); | ||
| testDir.EnumerateDirectories() | ||
| .ToList().ForEach(d => d.Delete(true)); | ||
| } | ||
|
|
||
| // Check the primary object/file tables and views which are populated by the general | ||
| // object handling of the analyzer (e.g. nothing BuildReport specific) | ||
| // This test is parameterized to run with and without "--skip-references" | ||
| // in order to show that the core object tables are not impacted by whether | ||
| // or not references are tracked. | ||
| [Test] | ||
| public async Task Analyze_BuildReport_ContainsExpected_ObjectInfo( | ||
| [Values(false, true)] bool skipReferences) | ||
| { | ||
| // This folder contains a reference build report generated by a build of the TestProject | ||
| // in the BuildReportInspector package. | ||
| var path = Path.Combine(m_TestDataFolder, "BuildReport1"); | ||
| var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); | ||
|
|
||
| var args = new List<string> { "analyze", path, "-p", "*.buildreport" }; | ||
| if (skipReferences) | ||
| args.Add("--skip-references"); | ||
|
|
||
| Assert.AreEqual(0, await Program.Main(args.ToArray())); | ||
| using var db = SQLTestHelper.OpenDatabase(databasePath); | ||
|
|
||
| // Sanity check the Unity objects found in this Build report file | ||
| // Tip: The meaning of the hard coded type ids used in the queries can be found | ||
| // at https://docs.unity3d.com/6000.3/Documentation/Manual/ClassIDReference.html | ||
|
|
||
| // The BuildReport object is the most important. | ||
| // PackedAssets objects are present for each output serialized file, .resS and .resource. | ||
| const int packedAssetCount = 7; | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 1125", 1, | ||
| "Unexpected number of BuildReport objects (type 1125)"); | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 1126", packedAssetCount, | ||
| "Unexpected number of PackedAssets objects"); | ||
|
|
||
| // This object is expected inside AssetBundle builds | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 668709126", 1, | ||
| "Unexpected number of BuiltAssetBundleInfoSet objects"); | ||
|
|
||
| // There can be other more obscure objects present, depending on the build, | ||
| // e.g. PluginBuildInfo, AudioBuildInfo, VideoBuildInfo etc. | ||
| var ttlObjCount = SQLTestHelper.QueryInt(db, "SELECT COUNT(*) FROM objects"); | ||
| Assert.That(ttlObjCount, Is.GreaterThanOrEqualTo(1+ packedAssetCount + 1), | ||
| "Unexpected number of objects in BuildReport analysis"); | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM asset_bundles", 0, | ||
| "Expected no AssetBundles found in reference folder"); | ||
|
|
||
| // | ||
| // Tests using object_view which lets us refer to objects by type name | ||
| // | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'BuildReport'", 1, | ||
| "Expected exactly one BuildReport in object_view"); | ||
|
|
||
| SQLTestHelper.AssertQueryString(db, "SELECT name FROM object_view WHERE type = 'BuildReport'", "Build AssetBundles", | ||
| "Unexpected name"); | ||
|
|
||
| SQLTestHelper.AssertQueryString(db, "SELECT name FROM object_view WHERE type = 'BuildReport'", "Build AssetBundles", | ||
| "Unexpected BuildReport name in object_view"); | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'PackedAssets'", packedAssetCount, | ||
| "Unexpected number of PackedAssets in object_view"); | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'BuiltAssetBundleInfoSet'", 1, | ||
| "Expected exactly one BuiltAssetBundleInfoSet in object_view"); | ||
|
|
||
| // Verify all rows have the same serialized_file | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(DISTINCT serialized_file) FROM object_view", 1, | ||
| "All objects should be from the same serialized file"); | ||
|
|
||
| SQLTestHelper.AssertQueryString(db, "SELECT DISTINCT serialized_file FROM object_view", "LastBuild.buildreport", | ||
| "Unexpected serialized file name in object_view"); | ||
|
|
||
| // Verify the BuildReport object has expected properties | ||
| var buildReportSize = SQLTestHelper.QueryInt(db, "SELECT size FROM object_view WHERE type = 'BuildReport'"); | ||
| Assert.That(buildReportSize, Is.GreaterThan(0), "BuildReport size should be greater than 0"); | ||
|
|
||
| // | ||
| // Tests using view_breakdown_by_type which aggregates objects by type | ||
| // | ||
|
|
||
| // Verify counts match for specific types | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT count FROM view_breakdown_by_type WHERE type = 'BuildReport'", 1, | ||
| "Expected 1 BuildReport in breakdown view"); | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT count FROM view_breakdown_by_type WHERE type = 'PackedAssets'", packedAssetCount, | ||
| "Expected 7 PackedAssets in breakdown view"); | ||
|
|
||
| var buildReportSize2 = SQLTestHelper.QueryInt(db, "SELECT byte_size FROM view_breakdown_by_type WHERE type = 'BuildReport'"); | ||
| Assert.AreEqual(buildReportSize, buildReportSize2, "Mismatch between object_view and breakdown_view for BuildReport size"); | ||
|
|
||
| // Verify pretty_size formatting exists | ||
| var buildReportPrettySize = SQLTestHelper.QueryString(db, "SELECT pretty_size FROM view_breakdown_by_type WHERE type = 'BuildReport'"); | ||
| Assert.That(buildReportPrettySize, Does.Contain("KB").Or.Contain("B"), "BuildReport pretty_size should have size unit"); | ||
|
|
||
| // Verify total byte_size across all types | ||
| var totalSize = SQLTestHelper.QueryInt(db, "SELECT SUM(byte_size) FROM view_breakdown_by_type"); | ||
| Assert.That(totalSize, Is.GreaterThan(buildReportSize), | ||
| "Unexpected number of objects in BuildReport analysis"); | ||
|
|
||
| // | ||
| // Tests using serialized_files table | ||
| // | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM serialized_files", 1, | ||
| "Expected exactly one serialized file"); | ||
|
|
||
| SQLTestHelper.AssertQueryString(db, "SELECT name FROM serialized_files WHERE id = 0", "LastBuild.buildreport", | ||
| "Unexpected serialized file name"); | ||
|
|
||
| // Verify asset_bundle column is empty/NULL for BuildReport files (they are not asset bundles) | ||
| var assetBundleValue = SQLTestHelper.QueryString(db, "SELECT COALESCE(asset_bundle, '') FROM serialized_files WHERE id = 0"); | ||
| Assert.That(string.IsNullOrEmpty(assetBundleValue), "BuildReport serialized file should not have asset_bundle value"); | ||
|
|
||
| // Verify the serialized file name matches what we see in object_view | ||
| var serializedFileName = SQLTestHelper.QueryString(db, "SELECT name FROM serialized_files WHERE id = 0"); | ||
| var objectViewFileName = SQLTestHelper.QueryString(db, "SELECT DISTINCT serialized_file FROM object_view"); | ||
| Assert.AreEqual(serializedFileName, objectViewFileName, | ||
| "Serialized file name should match between serialized_files table and object_view"); | ||
| } | ||
|
|
||
| // The BuildReport file has a simple structure with a single BuildReport object | ||
| // and all other objects referenced from its Appendicies array. | ||
| // This gives an opportunity for a detailed test that the "refs" table is properly populated. | ||
| [Test] | ||
| public async Task Analyze_BuildReport_ContainsExpectedReferences( | ||
| [Values(false, true)] bool skipReferences) | ||
| { | ||
| var path = Path.Combine(m_TestDataFolder, "BuildReport1"); | ||
| var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); | ||
|
|
||
| var args = new List<string> { "analyze", path, "-p", "*.buildreport" }; | ||
| if (skipReferences) | ||
| args.Add("--skip-references"); | ||
|
|
||
| Assert.AreEqual(0, await Program.Main(args.ToArray())); | ||
| using var db = SQLTestHelper.OpenDatabase(databasePath); | ||
|
|
||
| if (skipReferences) | ||
| { | ||
| // When --skip-references is used, the refs table should be empty | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM refs", 0, | ||
| "refs table should be empty when --skip-references is used"); | ||
| return; | ||
| } | ||
|
|
||
| var buildReportId = SQLTestHelper.QueryInt(db, | ||
| "SELECT id FROM objects WHERE type = 1125"); | ||
|
|
||
| var totalObjectCount = SQLTestHelper.QueryInt(db, "SELECT COUNT(*) FROM objects"); | ||
|
|
||
| var expectedRefCount = totalObjectCount - 1; | ||
| SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM refs", expectedRefCount, | ||
| "BuildReport should reference all other objects"); | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, $"SELECT COUNT(*) FROM refs WHERE object = {buildReportId}", expectedRefCount, | ||
| "All references should originate from BuildReport object"); | ||
|
|
||
| SQLTestHelper.AssertQueryInt(db, $"SELECT COUNT(*) FROM refs WHERE referenced_object = {buildReportId}", 0, | ||
| "No object should reference the BuildReport object"); | ||
|
|
||
| var refsWithWrongPath = SQLTestHelper.QueryInt(db, | ||
| "SELECT COUNT(*) FROM refs WHERE property_path NOT LIKE 'm_Appendices[%]'"); | ||
| Assert.AreEqual(0, refsWithWrongPath, "All property_path values should match pattern 'm_Appendices[N]'"); | ||
|
|
||
| SQLTestHelper.AssertQueryString(db, "SELECT DISTINCT property_type FROM refs", "Object", | ||
| "All references should have property_type 'Object'"); | ||
|
|
||
| var objectsNotReferenced = SQLTestHelper.QueryInt(db, | ||
| $@"SELECT COUNT(*) FROM objects | ||
| WHERE id != {buildReportId} | ||
| AND id NOT IN (SELECT referenced_object FROM refs)"); | ||
| Assert.AreEqual(0, objectsNotReferenced, | ||
| "Every object except BuildReport should be referenced exactly once"); | ||
|
|
||
| var duplicateRefs = SQLTestHelper.QueryInt(db, | ||
| "SELECT COUNT(*) FROM (SELECT referenced_object, COUNT(*) as cnt FROM refs GROUP BY referenced_object HAVING cnt > 1)"); | ||
| Assert.AreEqual(0, duplicateRefs, | ||
| "No object should be referenced more than once"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| using System.IO; | ||
| using Microsoft.Data.Sqlite; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace UnityDataTools.UnityDataTool.Tests; | ||
|
|
||
| #pragma warning disable NUnit2005, NUnit2006 | ||
|
|
||
| /// <summary> | ||
| /// Helper methods for executing SQL queries against a DB created by "Analyze" | ||
| /// and validating results in tests. | ||
| /// </summary> | ||
| public static class SQLTestHelper | ||
| { | ||
| /// <summary> | ||
| /// Default database filename used in tests. | ||
| /// </summary> | ||
| public const string DefaultDatabaseName = "database.db"; | ||
|
|
||
| /// <summary> | ||
| /// Creates and opens a SQLite database connection with standard test settings. | ||
| /// </summary> | ||
| /// <param name="databasePath">The path to the database file.</param> | ||
| /// <returns>An opened SqliteConnection. Caller is responsible for disposing.</returns> | ||
| public static SqliteConnection OpenDatabase(string databasePath) | ||
| { | ||
| var db = new SqliteConnection(new SqliteConnectionStringBuilder | ||
| { | ||
| DataSource = databasePath, | ||
| Mode = SqliteOpenMode.ReadWriteCreate, | ||
| Pooling = false, | ||
| ForeignKeys = false, | ||
| }.ConnectionString); | ||
| db.Open(); | ||
| return db; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the standard database path for tests (testOutputFolder/database.db). | ||
| /// </summary> | ||
| /// <param name="testOutputFolder">The test output folder path.</param> | ||
| /// <returns>The full path to the database file.</returns> | ||
| public static string GetDatabasePath(string testOutputFolder) | ||
| { | ||
| return Path.Combine(testOutputFolder, DefaultDatabaseName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes a SQL query and returns the integer result. | ||
| /// </summary> | ||
| /// <param name="db">The database connection to use.</param> | ||
| /// <param name="sql">The SQL query to execute (should return a single integer value).</param> | ||
| /// <returns>The integer result of the query.</returns> | ||
| public static int QueryInt(SqliteConnection db, string sql) | ||
| { | ||
| using var cmd = db.CreateCommand(); | ||
| cmd.CommandText = sql; | ||
| using var reader = cmd.ExecuteReader(); | ||
| reader.Read(); | ||
| return reader.GetInt32(0); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes a SQL query and returns the string result. | ||
| /// </summary> | ||
| /// <param name="db">The database connection to use.</param> | ||
| /// <param name="sql">The SQL query to execute (should return a single string value).</param> | ||
| /// <returns>The string result of the query.</returns> | ||
| public static string QueryString(SqliteConnection db, string sql) | ||
| { | ||
| using var cmd = db.CreateCommand(); | ||
| cmd.CommandText = sql; | ||
cycode-security[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using var reader = cmd.ExecuteReader(); | ||
| reader.Read(); | ||
| return reader.GetString(0); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes a SQL query and asserts the result equals the expected integer value. | ||
| /// </summary> | ||
| /// <param name="db">The database connection to use.</param> | ||
| /// <param name="sql">The SQL query to execute (should return a single integer value).</param> | ||
| /// <param name="expectedValue">The expected integer result.</param> | ||
| /// <param name="description">Description of what is being tested (used in assertion message).</param> | ||
| public static void AssertQueryInt(SqliteConnection db, string sql, int expectedValue, string description) | ||
| { | ||
| using var cmd = db.CreateCommand(); | ||
| cmd.CommandText = sql; | ||
cycode-security[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using var reader = cmd.ExecuteReader(); | ||
| reader.Read(); | ||
| Assert.AreEqual(expectedValue, reader.GetInt32(0), description); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes a SQL query and asserts the result equals the expected string value. | ||
| /// </summary> | ||
| /// <param name="db">The database connection to use.</param> | ||
| /// <param name="sql">The SQL query to execute (should return a single string value).</param> | ||
| /// <param name="expectedValue">The expected string result.</param> | ||
| /// <param name="description">Description of what is being tested (used in assertion message).</param> | ||
| public static void AssertQueryString(SqliteConnection db, string sql, string expectedValue, string description) | ||
| { | ||
| using var cmd = db.CreateCommand(); | ||
| cmd.CommandText = sql; | ||
cycode-security[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using var reader = cmd.ExecuteReader(); | ||
| reader.Read(); | ||
| Assert.AreEqual(expectedValue, reader.GetString(0), description); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.