Skip to content

Commit 6b5f8f7

Browse files
Test work and initial BuildReport test (#42)
-Refactoring to improve syntax for running many checks against the database. -Establish a BuildReport test which show the default analyze behavior (before we add any special handling) -Add a small BuildReport file (in its binary format) as reference data for the tests. This was created using the TestProject from BuildReportInspector
1 parent 1335a36 commit 6b5f8f7

File tree

6 files changed

+350
-64
lines changed

6 files changed

+350
-64
lines changed
12 KB
Binary file not shown.

UnityDataTool.Tests/AddressablesBuildLayoutTests.cs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,43 +42,27 @@ public async Task Analyze_BuildLayout_ContainsExpectedSQLContent()
4242
// Addressables test project.
4343
// The test confirms some expected content in the database
4444
var path = Path.Combine(m_TestDataFolder, "AddressableBuildLayouts");
45-
46-
var databasePath = Path.Combine(m_TestOutputFolder, "database.db");
45+
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
4746

4847
Assert.AreEqual(0, await Program.Main(new string[] { "analyze", path, "-p", "*.json" }));
49-
using var db = new SqliteConnection(new SqliteConnectionStringBuilder
50-
{
51-
DataSource = databasePath,
52-
Mode = SqliteOpenMode.ReadWriteCreate,
53-
Pooling = false,
54-
ForeignKeys = false,
55-
}.ConnectionString);
56-
db.Open();
57-
58-
using var cmd = db.CreateCommand();
48+
using var db = SQLTestHelper.OpenDatabase(databasePath);
5949

6050
// Sanity check some expected content in the output SQLite database
61-
cmd.CommandText =
62-
@"SELECT
63-
(SELECT COUNT(*) FROM addressables_builds),
64-
(SELECT COUNT(*) FROM addressables_builds WHERE name = ""buildlayout_2025.01.28.16.35.01.json""),
65-
(SELECT unity_version FROM addressables_builds WHERE id = 1),
66-
(SELECT package_version FROM addressables_builds WHERE id = 1),
67-
(SELECT COUNT(*) FROM addressables_build_bundles WHERE build_id = 1 and name = ""samplepack1_assets_0.bundle""),
68-
(SELECT file_size FROM addressables_build_bundles WHERE build_id = 2 and name = ""samplepack1_assets_0.bundle""),
69-
(SELECT packing_mode FROM addressables_build_groups WHERE build_id = 1 and name = ""SamplePack1""),
70-
(SELECT COUNT(*) FROM asset_bundles)";
71-
72-
using var reader = cmd.ExecuteReader();
73-
reader.Read();
74-
75-
Assert.AreEqual(2, reader.GetInt32(0), "Unexpected number of builds");
76-
Assert.AreEqual(1, reader.GetInt32(1), "Failed to find build matching reference filename");
77-
Assert.AreEqual("6000.1.0b2", reader.GetString(2), "Unexpected Unity Version");
78-
Assert.AreEqual("com.unity.addressables: 2.2.2", reader.GetString(3), "Unexpected Addressables version");
79-
Assert.AreEqual(1, reader.GetInt32(4), "Expected to find specific AssetBundle by name");
80-
Assert.AreEqual(33824, reader.GetInt32(5), "Unexpected size for specific AssetBundle in build 2");
81-
Assert.AreEqual("PackSeparately", reader.GetString(6), "Unexpected packing_mode for group");
82-
Assert.AreEqual(0, reader.GetInt32(7), "Expected no AssetBundles found in reference folder");
51+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM addressables_builds", 2,
52+
"Unexpected number of builds");
53+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM addressables_builds WHERE name = \"buildlayout_2025.01.28.16.35.01.json\"", 1,
54+
"Failed to find build matching reference filename");
55+
SQLTestHelper.AssertQueryString(db, "SELECT unity_version FROM addressables_builds WHERE id = 1", "6000.1.0b2",
56+
"Unexpected Unity Version");
57+
SQLTestHelper.AssertQueryString(db, "SELECT package_version FROM addressables_builds WHERE id = 1", "com.unity.addressables: 2.2.2",
58+
"Unexpected Addressables version");
59+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM addressables_build_bundles WHERE build_id = 1 and name = \"samplepack1_assets_0.bundle\"", 1,
60+
"Expected to find specific AssetBundle by name");
61+
SQLTestHelper.AssertQueryInt(db, "SELECT file_size FROM addressables_build_bundles WHERE build_id = 2 and name = \"samplepack1_assets_0.bundle\"", 33824,
62+
"Unexpected size for specific AssetBundle in build 2");
63+
SQLTestHelper.AssertQueryString(db, "SELECT packing_mode FROM addressables_build_groups WHERE build_id = 1 and name = \"SamplePack1\"", "PackSeparately",
64+
"Unexpected packing_mode for group");
65+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM asset_bundles", 0,
66+
"Expected no AssetBundles found in reference folder");
8367
}
8468
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
using Microsoft.Data.Sqlite;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using NUnit.Framework;
6+
using System.Collections.Generic;
7+
8+
namespace UnityDataTools.UnityDataTool.Tests;
9+
10+
#pragma warning disable NUnit2005, NUnit2006
11+
12+
public class BuildReportTests
13+
{
14+
private string m_TestOutputFolder;
15+
private string m_TestDataFolder;
16+
17+
[OneTimeSetUp]
18+
public void OneTimeSetup()
19+
{
20+
m_TestOutputFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "test_folder");
21+
m_TestDataFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data");
22+
Directory.CreateDirectory(m_TestOutputFolder);
23+
Directory.SetCurrentDirectory(m_TestOutputFolder);
24+
}
25+
26+
[TearDown]
27+
public void Teardown()
28+
{
29+
SqliteConnection.ClearAllPools();
30+
31+
var testDir = new DirectoryInfo(m_TestOutputFolder);
32+
testDir.EnumerateFiles()
33+
.ToList().ForEach(f => f.Delete());
34+
testDir.EnumerateDirectories()
35+
.ToList().ForEach(d => d.Delete(true));
36+
}
37+
38+
// Check the primary object/file tables and views which are populated by the general
39+
// object handling of the analyzer (e.g. nothing BuildReport specific)
40+
// This test is parameterized to run with and without "--skip-references"
41+
// in order to show that the core object tables are not impacted by whether
42+
// or not references are tracked.
43+
[Test]
44+
public async Task Analyze_BuildReport_ContainsExpected_ObjectInfo(
45+
[Values(false, true)] bool skipReferences)
46+
{
47+
// This folder contains a reference build report generated by a build of the TestProject
48+
// in the BuildReportInspector package.
49+
var path = Path.Combine(m_TestDataFolder, "BuildReport1");
50+
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
51+
52+
var args = new List<string> { "analyze", path, "-p", "*.buildreport" };
53+
if (skipReferences)
54+
args.Add("--skip-references");
55+
56+
Assert.AreEqual(0, await Program.Main(args.ToArray()));
57+
using var db = SQLTestHelper.OpenDatabase(databasePath);
58+
59+
// Sanity check the Unity objects found in this Build report file
60+
// Tip: The meaning of the hard coded type ids used in the queries can be found
61+
// at https://docs.unity3d.com/6000.3/Documentation/Manual/ClassIDReference.html
62+
63+
// The BuildReport object is the most important.
64+
// PackedAssets objects are present for each output serialized file, .resS and .resource.
65+
const int packedAssetCount = 7;
66+
67+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 1125", 1,
68+
"Unexpected number of BuildReport objects (type 1125)");
69+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 1126", packedAssetCount,
70+
"Unexpected number of PackedAssets objects");
71+
72+
// This object is expected inside AssetBundle builds
73+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects WHERE type = 668709126", 1,
74+
"Unexpected number of BuiltAssetBundleInfoSet objects");
75+
76+
// There can be other more obscure objects present, depending on the build,
77+
// e.g. PluginBuildInfo, AudioBuildInfo, VideoBuildInfo etc.
78+
var ttlObjCount = SQLTestHelper.QueryInt(db, "SELECT COUNT(*) FROM objects");
79+
Assert.That(ttlObjCount, Is.GreaterThanOrEqualTo(1+ packedAssetCount + 1),
80+
"Unexpected number of objects in BuildReport analysis");
81+
82+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM asset_bundles", 0,
83+
"Expected no AssetBundles found in reference folder");
84+
85+
//
86+
// Tests using object_view which lets us refer to objects by type name
87+
//
88+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'BuildReport'", 1,
89+
"Expected exactly one BuildReport in object_view");
90+
91+
SQLTestHelper.AssertQueryString(db, "SELECT name FROM object_view WHERE type = 'BuildReport'", "Build AssetBundles",
92+
"Unexpected name");
93+
94+
SQLTestHelper.AssertQueryString(db, "SELECT name FROM object_view WHERE type = 'BuildReport'", "Build AssetBundles",
95+
"Unexpected BuildReport name in object_view");
96+
97+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'PackedAssets'", packedAssetCount,
98+
"Unexpected number of PackedAssets in object_view");
99+
100+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM object_view WHERE type = 'BuiltAssetBundleInfoSet'", 1,
101+
"Expected exactly one BuiltAssetBundleInfoSet in object_view");
102+
103+
// Verify all rows have the same serialized_file
104+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(DISTINCT serialized_file) FROM object_view", 1,
105+
"All objects should be from the same serialized file");
106+
107+
SQLTestHelper.AssertQueryString(db, "SELECT DISTINCT serialized_file FROM object_view", "LastBuild.buildreport",
108+
"Unexpected serialized file name in object_view");
109+
110+
// Verify the BuildReport object has expected properties
111+
var buildReportSize = SQLTestHelper.QueryInt(db, "SELECT size FROM object_view WHERE type = 'BuildReport'");
112+
Assert.That(buildReportSize, Is.GreaterThan(0), "BuildReport size should be greater than 0");
113+
114+
//
115+
// Tests using view_breakdown_by_type which aggregates objects by type
116+
//
117+
118+
// Verify counts match for specific types
119+
SQLTestHelper.AssertQueryInt(db, "SELECT count FROM view_breakdown_by_type WHERE type = 'BuildReport'", 1,
120+
"Expected 1 BuildReport in breakdown view");
121+
SQLTestHelper.AssertQueryInt(db, "SELECT count FROM view_breakdown_by_type WHERE type = 'PackedAssets'", packedAssetCount,
122+
"Expected 7 PackedAssets in breakdown view");
123+
124+
var buildReportSize2 = SQLTestHelper.QueryInt(db, "SELECT byte_size FROM view_breakdown_by_type WHERE type = 'BuildReport'");
125+
Assert.AreEqual(buildReportSize, buildReportSize2, "Mismatch between object_view and breakdown_view for BuildReport size");
126+
127+
// Verify pretty_size formatting exists
128+
var buildReportPrettySize = SQLTestHelper.QueryString(db, "SELECT pretty_size FROM view_breakdown_by_type WHERE type = 'BuildReport'");
129+
Assert.That(buildReportPrettySize, Does.Contain("KB").Or.Contain("B"), "BuildReport pretty_size should have size unit");
130+
131+
// Verify total byte_size across all types
132+
var totalSize = SQLTestHelper.QueryInt(db, "SELECT SUM(byte_size) FROM view_breakdown_by_type");
133+
Assert.That(totalSize, Is.GreaterThan(buildReportSize),
134+
"Unexpected number of objects in BuildReport analysis");
135+
136+
//
137+
// Tests using serialized_files table
138+
//
139+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM serialized_files", 1,
140+
"Expected exactly one serialized file");
141+
142+
SQLTestHelper.AssertQueryString(db, "SELECT name FROM serialized_files WHERE id = 0", "LastBuild.buildreport",
143+
"Unexpected serialized file name");
144+
145+
// Verify asset_bundle column is empty/NULL for BuildReport files (they are not asset bundles)
146+
var assetBundleValue = SQLTestHelper.QueryString(db, "SELECT COALESCE(asset_bundle, '') FROM serialized_files WHERE id = 0");
147+
Assert.That(string.IsNullOrEmpty(assetBundleValue), "BuildReport serialized file should not have asset_bundle value");
148+
149+
// Verify the serialized file name matches what we see in object_view
150+
var serializedFileName = SQLTestHelper.QueryString(db, "SELECT name FROM serialized_files WHERE id = 0");
151+
var objectViewFileName = SQLTestHelper.QueryString(db, "SELECT DISTINCT serialized_file FROM object_view");
152+
Assert.AreEqual(serializedFileName, objectViewFileName,
153+
"Serialized file name should match between serialized_files table and object_view");
154+
}
155+
156+
// The BuildReport file has a simple structure with a single BuildReport object
157+
// and all other objects referenced from its Appendicies array.
158+
// This gives an opportunity for a detailed test that the "refs" table is properly populated.
159+
[Test]
160+
public async Task Analyze_BuildReport_ContainsExpectedReferences(
161+
[Values(false, true)] bool skipReferences)
162+
{
163+
var path = Path.Combine(m_TestDataFolder, "BuildReport1");
164+
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
165+
166+
var args = new List<string> { "analyze", path, "-p", "*.buildreport" };
167+
if (skipReferences)
168+
args.Add("--skip-references");
169+
170+
Assert.AreEqual(0, await Program.Main(args.ToArray()));
171+
using var db = SQLTestHelper.OpenDatabase(databasePath);
172+
173+
if (skipReferences)
174+
{
175+
// When --skip-references is used, the refs table should be empty
176+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM refs", 0,
177+
"refs table should be empty when --skip-references is used");
178+
return;
179+
}
180+
181+
var buildReportId = SQLTestHelper.QueryInt(db,
182+
"SELECT id FROM objects WHERE type = 1125");
183+
184+
var totalObjectCount = SQLTestHelper.QueryInt(db, "SELECT COUNT(*) FROM objects");
185+
186+
var expectedRefCount = totalObjectCount - 1;
187+
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM refs", expectedRefCount,
188+
"BuildReport should reference all other objects");
189+
190+
SQLTestHelper.AssertQueryInt(db, $"SELECT COUNT(*) FROM refs WHERE object = {buildReportId}", expectedRefCount,
191+
"All references should originate from BuildReport object");
192+
193+
SQLTestHelper.AssertQueryInt(db, $"SELECT COUNT(*) FROM refs WHERE referenced_object = {buildReportId}", 0,
194+
"No object should reference the BuildReport object");
195+
196+
var refsWithWrongPath = SQLTestHelper.QueryInt(db,
197+
"SELECT COUNT(*) FROM refs WHERE property_path NOT LIKE 'm_Appendices[%]'");
198+
Assert.AreEqual(0, refsWithWrongPath, "All property_path values should match pattern 'm_Appendices[N]'");
199+
200+
SQLTestHelper.AssertQueryString(db, "SELECT DISTINCT property_type FROM refs", "Object",
201+
"All references should have property_type 'Object'");
202+
203+
var objectsNotReferenced = SQLTestHelper.QueryInt(db,
204+
$@"SELECT COUNT(*) FROM objects
205+
WHERE id != {buildReportId}
206+
AND id NOT IN (SELECT referenced_object FROM refs)");
207+
Assert.AreEqual(0, objectsNotReferenced,
208+
"Every object except BuildReport should be referenced exactly once");
209+
210+
var duplicateRefs = SQLTestHelper.QueryInt(db,
211+
"SELECT COUNT(*) FROM (SELECT referenced_object, COUNT(*) as cnt FROM refs GROUP BY referenced_object HAVING cnt > 1)");
212+
Assert.AreEqual(0, duplicateRefs,
213+
"No object should be referenced more than once");
214+
}
215+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System.IO;
2+
using Microsoft.Data.Sqlite;
3+
using NUnit.Framework;
4+
5+
namespace UnityDataTools.UnityDataTool.Tests;
6+
7+
#pragma warning disable NUnit2005, NUnit2006
8+
9+
/// <summary>
10+
/// Helper methods for executing SQL queries against a DB created by "Analyze"
11+
/// and validating results in tests.
12+
/// </summary>
13+
public static class SQLTestHelper
14+
{
15+
/// <summary>
16+
/// Default database filename used in tests.
17+
/// </summary>
18+
public const string DefaultDatabaseName = "database.db";
19+
20+
/// <summary>
21+
/// Creates and opens a SQLite database connection with standard test settings.
22+
/// </summary>
23+
/// <param name="databasePath">The path to the database file.</param>
24+
/// <returns>An opened SqliteConnection. Caller is responsible for disposing.</returns>
25+
public static SqliteConnection OpenDatabase(string databasePath)
26+
{
27+
var db = new SqliteConnection(new SqliteConnectionStringBuilder
28+
{
29+
DataSource = databasePath,
30+
Mode = SqliteOpenMode.ReadWriteCreate,
31+
Pooling = false,
32+
ForeignKeys = false,
33+
}.ConnectionString);
34+
db.Open();
35+
return db;
36+
}
37+
38+
/// <summary>
39+
/// Gets the standard database path for tests (testOutputFolder/database.db).
40+
/// </summary>
41+
/// <param name="testOutputFolder">The test output folder path.</param>
42+
/// <returns>The full path to the database file.</returns>
43+
public static string GetDatabasePath(string testOutputFolder)
44+
{
45+
return Path.Combine(testOutputFolder, DefaultDatabaseName);
46+
}
47+
48+
/// <summary>
49+
/// Executes a SQL query and returns the integer result.
50+
/// </summary>
51+
/// <param name="db">The database connection to use.</param>
52+
/// <param name="sql">The SQL query to execute (should return a single integer value).</param>
53+
/// <returns>The integer result of the query.</returns>
54+
public static int QueryInt(SqliteConnection db, string sql)
55+
{
56+
using var cmd = db.CreateCommand();
57+
cmd.CommandText = sql;
58+
using var reader = cmd.ExecuteReader();
59+
reader.Read();
60+
return reader.GetInt32(0);
61+
}
62+
63+
/// <summary>
64+
/// Executes a SQL query and returns the string result.
65+
/// </summary>
66+
/// <param name="db">The database connection to use.</param>
67+
/// <param name="sql">The SQL query to execute (should return a single string value).</param>
68+
/// <returns>The string result of the query.</returns>
69+
public static string QueryString(SqliteConnection db, string sql)
70+
{
71+
using var cmd = db.CreateCommand();
72+
cmd.CommandText = sql;
73+
using var reader = cmd.ExecuteReader();
74+
reader.Read();
75+
return reader.GetString(0);
76+
}
77+
78+
/// <summary>
79+
/// Executes a SQL query and asserts the result equals the expected integer value.
80+
/// </summary>
81+
/// <param name="db">The database connection to use.</param>
82+
/// <param name="sql">The SQL query to execute (should return a single integer value).</param>
83+
/// <param name="expectedValue">The expected integer result.</param>
84+
/// <param name="description">Description of what is being tested (used in assertion message).</param>
85+
public static void AssertQueryInt(SqliteConnection db, string sql, int expectedValue, string description)
86+
{
87+
using var cmd = db.CreateCommand();
88+
cmd.CommandText = sql;
89+
using var reader = cmd.ExecuteReader();
90+
reader.Read();
91+
Assert.AreEqual(expectedValue, reader.GetInt32(0), description);
92+
}
93+
94+
/// <summary>
95+
/// Executes a SQL query and asserts the result equals the expected string value.
96+
/// </summary>
97+
/// <param name="db">The database connection to use.</param>
98+
/// <param name="sql">The SQL query to execute (should return a single string value).</param>
99+
/// <param name="expectedValue">The expected string result.</param>
100+
/// <param name="description">Description of what is being tested (used in assertion message).</param>
101+
public static void AssertQueryString(SqliteConnection db, string sql, string expectedValue, string description)
102+
{
103+
using var cmd = db.CreateCommand();
104+
cmd.CommandText = sql;
105+
using var reader = cmd.ExecuteReader();
106+
reader.Read();
107+
Assert.AreEqual(expectedValue, reader.GetString(0), description);
108+
}
109+
}

0 commit comments

Comments
 (0)