< Summary

Class:DCL.ABConverter.VisualTests
Assembly:ABConverter
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/ABConverter/VisualTests.cs
Covered lines:0
Uncovered lines:152
Coverable lines:152
Total lines:341
Line coverage:0% (0 of 152)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
VisualTests()0%2100%
TestConvertedAssets()0%5522300%
SkipAllAssets()0%2100%
TakeObjectSnapshot()0%20400%
LoadAndInstantiateAllGltfAssets()0%6200%
LoadAndInstantiateAllAssetBundles()0%3421800%
PatchSkeletonlessSkinnedMeshRenderer(...)0%12300%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/ABConverter/VisualTests.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using System.IO;
 4using DCL.Helpers;
 5using System;
 6using UnityEditor;
 7using UnityEditor.SceneManagement;
 8using UnityEngine;
 9using UnityEngine.Networking;
 10using Object = UnityEngine.Object;
 11
 12namespace DCL.ABConverter
 13{
 14    public static class VisualTests
 15    {
 016        static readonly string baselinePath = AssetBundlesVisualTestHelpers.baselineImagesPath;
 017        static readonly string testImagesPath = AssetBundlesVisualTestHelpers.testImagesPath;
 018        static string abPath = Application.dataPath + "/../AssetBundles/";
 019        static int skippedAssets = 0;
 20
 21        /// <summary>
 22        /// Instantiate all locally-converted GLTFs in both formats (GLTF and Asset Bundle) and
 23        /// compare them visually. If a visual test fails, the AB is deleted to avoid uploading it
 24        /// </summary>
 25        public static IEnumerator TestConvertedAssets(Environment env = null, Action<int> OnFinish = null)
 26        {
 027            if (Utils.ParseOption(Config.CLI_SET_CUSTOM_OUTPUT_ROOT_PATH, 1, out string[] outputPath))
 28            {
 029                abPath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), outputPath[0] + "/");
 30
 031                Debug.Log($"Visual Test Detection: -output PATH param found, setting ABPath as '{abPath}'");
 032            }
 33            else
 34            {
 035                Debug.Log($"Visual Test Detection: -output PATH param NOT found, setting ABPath as '{abPath}'");
 36            }
 37
 038            if (!System.IO.Directory.Exists(abPath))
 39            {
 040                Debug.Log($"Visual Test Detection: ABs path '{abPath}' doesn't exist...");
 041                SkipAllAssets();
 042                OnFinish?.Invoke(skippedAssets);
 043                yield break;
 44            }
 45
 046            Debug.Log("Visual Test Detection: Starting converted assets testing...");
 47
 048            var scene = EditorSceneManager.OpenScene($"Assets/ABConverter/VisualTestScene.unity", OpenSceneMode.Single);
 049            yield return new WaitUntil(() => scene.isLoaded);
 50
 051            AssetBundlesVisualTestHelpers.baselineImagesPath += "ABConverter/";
 052            AssetBundlesVisualTestHelpers.testImagesPath += "ABConverter/";
 053            skippedAssets = 0;
 54
 055            var gltfs = LoadAndInstantiateAllGltfAssets();
 56
 057            if (gltfs.Length == 0)
 58            {
 059                Debug.Log("Visual Test Detection: no instantiated GLTFs...");
 060                SkipAllAssets();
 061                OnFinish?.Invoke(skippedAssets);
 062                yield break;
 63            }
 64
 65            // Take prewarm snapshot to make sure the scene is correctly loaded
 066            yield return TakeObjectSnapshot(new GameObject(), $"ABConverter_Warmup.png");
 67
 068            AssetBundlesVisualTestHelpers.generateBaseline = true;
 69
 070            foreach (GameObject go in gltfs)
 71            {
 072                go.SetActive(false);
 73            }
 74
 075            foreach (GameObject go in gltfs)
 76            {
 077                go.SetActive(true);
 78
 079                yield return TakeObjectSnapshot(go, $"ABConverter_{go.name}.png");
 80
 081                go.SetActive(false);
 082            }
 83
 084            foreach (GameObject go in gltfs)
 85            {
 086                go.SetActive(true);
 87
 088                yield return TakeObjectSnapshot(go, $"ABConverter_{go.name}.png");
 89
 090                go.SetActive(false);
 091            }
 92
 093            AssetBundlesVisualTestHelpers.generateBaseline = false;
 94
 095            var abs = LoadAndInstantiateAllAssetBundles();
 96
 097            if (abs.Length == 0)
 98            {
 099                Debug.Log("Visual Test Detection: no instantiated ABs...");
 0100                SkipAllAssets();
 0101                OnFinish?.Invoke(skippedAssets);
 0102                yield break;
 103            }
 104
 0105            foreach (GameObject go in abs)
 106            {
 0107                go.SetActive(false);
 108            }
 109
 0110            foreach (GameObject go in abs)
 111            {
 0112                string testName = $"ABConverter_{go.name}.png";
 113
 0114                go.SetActive(true);
 115
 0116                yield return TakeObjectSnapshot(go, testName);
 117
 0118                bool result = AssetBundlesVisualTestHelpers.TestSnapshot(
 119                    AssetBundlesVisualTestHelpers.baselineImagesPath + testName,
 120                    AssetBundlesVisualTestHelpers.testImagesPath + testName,
 121                    95,
 122                    false);
 123
 124                // Delete failed AB files to avoid uploading them
 0125                if (!result && env != null)
 126                {
 0127                    string filePath = abPath + go.name;
 0128                    if (env.file.Exists(filePath))
 129                    {
 0130                        env.file.Delete(filePath);
 0131                        env.file.Delete(filePath + ".depmap");
 132                    }
 133
 0134                    skippedAssets++;
 135
 136                    // TODO: Notify some metrics API or something to let us know that this asset has conversion problems
 0137                    Debug.Log("Visual Test Detection: FAILED converting asset -> " + go.name);
 138                }
 139
 0140                go.SetActive(false);
 0141            }
 142
 0143            AssetBundlesVisualTestHelpers.baselineImagesPath = baselinePath;
 0144            AssetBundlesVisualTestHelpers.testImagesPath = testImagesPath;
 145
 0146            Debug.Log("Visual Test Detection: Finished converted assets testing...skipped assets: " + skippedAssets);
 0147            OnFinish?.Invoke(skippedAssets);
 0148        }
 149
 150        /// <summary>
 151        /// Set skippedAssets to the amount of target assets
 152        /// </summary>
 0153        private static void SkipAllAssets() { skippedAssets = AssetDatabase.FindAssets($"t:GameObject", new[] { "Assets/
 154
 155        /// <summary>
 156        /// Position camera based on renderer bounds and take snapshot
 157        /// </summary>
 158        private static IEnumerator TakeObjectSnapshot(GameObject targetGO, string testName)
 159        {
 0160            Vector3 originalScale = targetGO.transform.localScale;
 0161            var renderers = targetGO.GetComponentsInChildren<Renderer>();
 162
 163            // unify all child renderer bounds and use that to position the snapshot camera
 0164            var mergedBounds = MeshUtils.BuildMergedBounds(renderers);
 165
 166            // Some objects are imported super small (like 0.00x in scale) and we can barely see them in the snapshots
 0167            if (mergedBounds.size.magnitude < 1f)
 168            {
 0169                targetGO.transform.localScale *= 100;
 0170                mergedBounds = MeshUtils.BuildMergedBounds(renderers);
 171            }
 172
 0173            Vector3 offset = mergedBounds.extents;
 0174            offset.x = Mathf.Max(1, offset.x);
 0175            offset.y = Mathf.Max(1, offset.y);
 0176            offset.z = Mathf.Max(1, offset.z);
 177
 0178            Vector3 cameraPosition = new Vector3(mergedBounds.min.x - offset.x, mergedBounds.max.y + offset.y, mergedBou
 179
 0180            yield return AssetBundlesVisualTestHelpers.TakeSnapshot(testName, Camera.main, cameraPosition, mergedBounds.
 181
 0182            targetGO.transform.localScale = originalScale;
 0183        }
 184
 185        /// <summary>
 186        /// Instantiate all local GLTFs found in the "_Downloaded" directory
 187        /// </summary>
 188        public static GameObject[] LoadAndInstantiateAllGltfAssets()
 189        {
 0190            var assets = AssetDatabase.FindAssets($"t:GameObject", new[] { "Assets/_Downloaded" });
 191
 0192            List<GameObject> importedGLTFs = new List<GameObject>();
 193
 0194            foreach (var guid in assets)
 195            {
 0196                GameObject gltf = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
 0197                var importedGLTF = Object.Instantiate(gltf);
 0198                importedGLTF.name = importedGLTF.name.Replace("(Clone)", "");
 199
 0200                PatchSkeletonlessSkinnedMeshRenderer(importedGLTF.gameObject.GetComponentInChildren<SkinnedMeshRenderer>
 201
 0202                importedGLTFs.Add(importedGLTF);
 203            }
 204
 0205            return importedGLTFs.ToArray();
 206        }
 207
 208        /// <summary>
 209        /// Search for local GLTFs in "_Downloaded" and use those hashes to find their corresponding
 210        /// Asset Bundle files, then instantiate those ABs in the Unity scene
 211        /// </summary>
 212        public static GameObject[] LoadAndInstantiateAllAssetBundles()
 213        {
 0214            Caching.ClearCache();
 215
 0216            string workingFolderName = "_Downloaded";
 217
 0218            var pathList = Directory.GetDirectories(Application.dataPath + "/" + workingFolderName);
 219
 0220            List<string> dependencyAbs = new List<string>();
 0221            List<string> mainAbs = new List<string>();
 222
 0223            foreach (var paths in pathList)
 224            {
 0225                var hash = new DirectoryInfo(paths).Name;
 0226                var path = "Assets/" + workingFolderName + "/" + hash;
 0227                var guids = AssetDatabase.FindAssets("t:GameObject", new[] { path });
 228
 229                // NOTE(Brian): If no gameObjects are found, we assume they are dependency assets (textures, etc).
 0230                if (guids.Length == 0)
 231                {
 232                    // We need to avoid adding dependencies that are NOT converted to ABs (like .bin files)
 0233                    if (AssetDatabase.FindAssets("t:Texture", new[] { path }).Length != 0)
 234                    {
 0235                        dependencyAbs.Add(hash);
 236                    }
 0237                }
 238                else
 239                {
 240                    // Otherwise we assume they are gltfs.
 0241                    mainAbs.Add(hash);
 242                }
 243            }
 244
 245            // NOTE(Brian): We need to store the asset bundles so they can be unloaded later.
 0246            List<AssetBundle> loadedAbs = new List<AssetBundle>();
 247
 0248            foreach (var hash in dependencyAbs)
 249            {
 0250                string path = abPath + hash;
 0251                var req = UnityWebRequestAssetBundle.GetAssetBundle(path);
 252
 0253                if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX || SystemInfo.operatingSystemFamily
 0254                    req.url = req.url.Replace("http://localhost", "file:///");
 255
 0256                req.SendWebRequest();
 257
 0258                while (!req.isDone) { }
 259
 0260                if (!req.WebRequestSucceded())
 261                {
 0262                    Debug.Log("Visual Test Detection: Failed to download dependency asset: " + hash);
 0263                    continue;
 264                }
 265
 0266                var assetBundle = DownloadHandlerAssetBundle.GetContent(req);
 0267                assetBundle.LoadAllAssets();
 0268                loadedAbs.Add(assetBundle);
 269            }
 270
 0271            List<GameObject> results = new List<GameObject>();
 272
 0273            foreach (var hash in mainAbs)
 274            {
 0275                string path = abPath + hash;
 0276                var req = UnityWebRequestAssetBundle.GetAssetBundle(path);
 277
 0278                if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX || SystemInfo.operatingSystemFamily
 0279                    req.url = req.url.Replace("http://localhost", "file:///");
 280
 0281                req.SendWebRequest();
 282
 0283                while (!req.isDone) { }
 284
 0285                if (!req.WebRequestSucceded())
 286                {
 0287                    Debug.Log("Visual Test Detection: Failed to instantiate AB, missing source file for : " + hash);
 0288                    skippedAssets++;
 0289                    continue;
 290                }
 291
 0292                var assetBundle = DownloadHandlerAssetBundle.GetContent(req);
 0293                Object[] assets = assetBundle.LoadAllAssets();
 294
 0295                foreach (Object asset in assets)
 296                {
 0297                    if (asset is Material material)
 298                    {
 0299                        material.shader = Shader.Find("DCL/Universal Render Pipeline/Lit");
 300                    }
 301
 0302                    if (asset is GameObject assetAsGameObject)
 303                    {
 0304                        GameObject instance = Object.Instantiate(assetAsGameObject);
 305
 0306                        PatchSkeletonlessSkinnedMeshRenderer(instance.GetComponentInChildren<SkinnedMeshRenderer>());
 307
 0308                        results.Add(instance);
 0309                        instance.name = instance.name.Replace("(Clone)", "");
 310                    }
 311                }
 312
 0313                loadedAbs.Add(assetBundle);
 314            }
 315
 0316            foreach (var ab in loadedAbs)
 317            {
 0318                ab.Unload(false);
 319            }
 320
 0321            return results.ToArray();
 322        }
 323
 324        /// <summary>
 325        /// Wearables that are not body-shapes are optimized getting rid of the skeleton, so if this
 326        /// SkinnedMeshRenderer is missing its root bone, we replace the renderer to make it rendereable
 327        /// for the visual tests. In runtime, WearableController.SetAnimatorBones() takes care of the
 328        /// root bone setup.
 329        /// </summary>
 330        private static void PatchSkeletonlessSkinnedMeshRenderer(SkinnedMeshRenderer skinnedMeshRenderer)
 331        {
 0332            if (skinnedMeshRenderer == null || skinnedMeshRenderer.rootBone != null)
 0333                return;
 334
 0335            MeshRenderer meshRenderer = skinnedMeshRenderer.gameObject.AddComponent<MeshRenderer>();
 0336            meshRenderer.sharedMaterials = skinnedMeshRenderer.sharedMaterials;
 337
 0338            Object.DestroyImmediate(skinnedMeshRenderer);
 0339        }
 340    }
 341}