mirror of
https://github.com/ppy/osu.git
synced 2025-01-31 06:12:55 +08:00
Add extensive test scene for skin migrations
This is different from `SkinDeserialisationTest` in that layouts can be written programmatically with as much ease, allowing to test migration logic with different scenarios without running the game and exporting skins and attaching them to tests.
This commit is contained in:
parent
83ecfbd155
commit
c2215b10cf
225
osu.Game.Tests/Visual/Skinning/TestSceneSkinMigration.cs
Normal file
225
osu.Game.Tests/Visual/Skinning/TestSceneSkinMigration.cs
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Dummy;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Skinning
|
||||
{
|
||||
public partial class TestSceneSkinMigration : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Test]
|
||||
public void TestEmptyConfiguration()
|
||||
{
|
||||
LegacySkin skin = null!;
|
||||
|
||||
AddStep("load skin with empty configuration", () => skin = loadSkin<LegacySkin>());
|
||||
AddAssert("skin has no configuration", () => !skin.LayoutInfos.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSomeConfiguration()
|
||||
{
|
||||
LegacySkin skin = null!;
|
||||
|
||||
AddStep("load skin with some configuration", () =>
|
||||
{
|
||||
skin = loadSkin<LegacySkin>(new Dictionary<GlobalSkinnableContainers, SkinLayoutInfo>
|
||||
{
|
||||
{ GlobalSkinnableContainers.MainHUDComponents, createLayout(SkinLayoutInfo.LATEST_VERSION, [nameof(LegacyHealthDisplay)]) },
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("skin has correct configuration", () =>
|
||||
{
|
||||
return skin.LayoutInfos.Single().Value.DrawableInfo["global"].Select(d => d.Type.Name).SequenceEqual([nameof(BigBlackBox), nameof(LegacyHealthDisplay)]) &&
|
||||
skin.LayoutInfos.Single().Value.DrawableInfo.Where(d => d.Key != @"global")
|
||||
.All(d => d.Value.Single().Type.Name == nameof(BigBlackBox));
|
||||
});
|
||||
}
|
||||
|
||||
#region Version 1
|
||||
|
||||
[Test]
|
||||
public void TestMigration_1()
|
||||
{
|
||||
LegacySkin skin = null!;
|
||||
|
||||
AddStep("load skin", () =>
|
||||
{
|
||||
skin = loadSkin<LegacySkin>(new Dictionary<GlobalSkinnableContainers, SkinLayoutInfo>
|
||||
{
|
||||
{
|
||||
GlobalSkinnableContainers.MainHUDComponents,
|
||||
createLayout(0, [nameof(LegacyDefaultComboCounter)])
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("combo counter removed from global", () =>
|
||||
{
|
||||
var dict = skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].DrawableInfo;
|
||||
return dict.Single(kvp => kvp.Key == @"global").Value.Single().Type.Name == nameof(BigBlackBox);
|
||||
});
|
||||
AddAssert("combo counter moved to each ruleset", () =>
|
||||
{
|
||||
var dict = skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].DrawableInfo;
|
||||
return dict.Where(kvp => kvp.Key != @"global").All(kvp => kvp.Value.Select(d => d.Type.Name).SequenceEqual([nameof(BigBlackBox), nameof(LegacyDefaultComboCounter)]));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigration_1_NoComboCounter()
|
||||
{
|
||||
LegacySkin skin = null!;
|
||||
|
||||
AddStep("load skin", () =>
|
||||
{
|
||||
skin = loadSkin<LegacySkin>(new Dictionary<GlobalSkinnableContainers, SkinLayoutInfo>
|
||||
{
|
||||
{
|
||||
GlobalSkinnableContainers.MainHUDComponents,
|
||||
createLayout(0)
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("nothing removed from global", () =>
|
||||
{
|
||||
var dict = skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].DrawableInfo;
|
||||
return dict.Single(kvp => kvp.Key == @"global").Value.Single().Type.Name == nameof(BigBlackBox);
|
||||
});
|
||||
AddAssert("no combo counter added", () =>
|
||||
{
|
||||
var dict = skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].DrawableInfo;
|
||||
return dict.Where(kvp => kvp.Key != @"global").All(kvp => kvp.Value.Select(d => d.Type.Name).SequenceEqual([nameof(BigBlackBox)]));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SkinLayoutInfo createLayout(int version, string[]? globalComponents = null, string? ruleset = null, string[]? rulesetComponents = null)
|
||||
{
|
||||
var info = new SkinLayoutInfo
|
||||
{
|
||||
Version = version,
|
||||
DrawableInfo =
|
||||
{
|
||||
{ "global", globalComponents?.Select(c => resolveComponent(c).CreateSerialisedInfo()).ToArray() ?? Array.Empty<SerialisedDrawableInfo>() },
|
||||
}
|
||||
};
|
||||
|
||||
if (ruleset != null && rulesetComponents != null)
|
||||
info.DrawableInfo.Add(ruleset, rulesetComponents.Select(c => resolveComponent(c).CreateSerialisedInfo()).ToArray());
|
||||
|
||||
// add random drawable to ensure nothing is incorrectly discarded
|
||||
foreach (string key in rulesets.AvailableRulesets.Select(r => r.ShortName).Prepend(@"global"))
|
||||
{
|
||||
if (!info.DrawableInfo.TryGetValue(key, out var drawables))
|
||||
info.DrawableInfo.Add(key, drawables = Array.Empty<SerialisedDrawableInfo>());
|
||||
|
||||
info.DrawableInfo[key] = drawables.Prepend(new BigBlackBox().CreateSerialisedInfo()).ToArray();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private Drawable resolveComponent(string name, string? ruleset = null)
|
||||
{
|
||||
var drawables = SerialisedDrawableInfo.GetAllAvailableDrawables();
|
||||
|
||||
if (ruleset != null)
|
||||
drawables = drawables.Concat(SerialisedDrawableInfo.GetAllAvailableDrawables(rulesets.GetRuleset(ruleset))).ToArray();
|
||||
|
||||
return (Drawable)Activator.CreateInstance(drawables.Single(d => d.Name == name))!;
|
||||
}
|
||||
|
||||
private T loadSkin<T>(IDictionary<GlobalSkinnableContainers, SkinLayoutInfo>? layout = null)
|
||||
where T : Skin
|
||||
{
|
||||
var info = new TestSkinInfo(typeof(T).GetInvariantInstantiationInfo(), layout);
|
||||
return (T)info.CreateInstance(new TestStorageResourceProvider(layout, info.Files, Realm));
|
||||
}
|
||||
|
||||
private class TestSkinInfo : SkinInfo
|
||||
{
|
||||
public override IList<RealmNamedFileUsage> Files { get; } = new List<RealmNamedFileUsage>();
|
||||
|
||||
public TestSkinInfo(string instantiationInfo, IDictionary<GlobalSkinnableContainers, SkinLayoutInfo>? layout)
|
||||
: base("test skin", "me", instantiationInfo)
|
||||
{
|
||||
if (layout != null)
|
||||
{
|
||||
foreach (var kvp in layout)
|
||||
Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = Guid.NewGuid().ToString().ComputeMD5Hash() }, kvp.Key + ".json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestStorageResourceProvider : IStorageResourceProvider
|
||||
{
|
||||
public IRenderer Renderer { get; } = new DummyRenderer();
|
||||
public IResourceStore<byte[]> Resources { get; } = new ResourceStore<byte[]>();
|
||||
public IResourceStore<TextureUpload>? CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||
|
||||
public AudioManager? AudioManager => null;
|
||||
|
||||
public IResourceStore<byte[]> Files { get; }
|
||||
public RealmAccess RealmAccess { get; }
|
||||
|
||||
public TestStorageResourceProvider(IDictionary<GlobalSkinnableContainers, SkinLayoutInfo>? layout, IList<RealmNamedFileUsage> files, RealmAccess realm)
|
||||
{
|
||||
Files = new TestResourceStore(layout, files);
|
||||
RealmAccess = realm;
|
||||
}
|
||||
|
||||
private class TestResourceStore : ResourceStore<byte[]>
|
||||
{
|
||||
private readonly IDictionary<GlobalSkinnableContainers, SkinLayoutInfo>? layout;
|
||||
private readonly IList<RealmNamedFileUsage> files;
|
||||
|
||||
public TestResourceStore(IDictionary<GlobalSkinnableContainers, SkinLayoutInfo>? layout, IList<RealmNamedFileUsage> files)
|
||||
{
|
||||
this.layout = layout;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public override byte[] Get(string name)
|
||||
{
|
||||
string? filename = files.SingleOrDefault(f => f.File.GetStoragePath() == name)?.Filename;
|
||||
if (filename == null || layout == null)
|
||||
return base.Get(name);
|
||||
|
||||
if (!Enum.TryParse<GlobalSkinnableContainers>(filename.Replace(@".json", string.Empty), out var type) ||
|
||||
!layout.TryGetValue(type, out var info))
|
||||
return base.Get(name);
|
||||
|
||||
string json = JsonConvert.SerializeObject(info, new JsonSerializerSettings { Formatting = Formatting.Indented });
|
||||
return Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ namespace osu.Game.Skinning
|
||||
return (Skin)Activator.CreateInstance(type, this, resources)!;
|
||||
}
|
||||
|
||||
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
||||
public virtual IList<RealmNamedFileUsage> Files { get; } = null!;
|
||||
|
||||
public bool DeletePending { get; set; }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user