1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-15 17:47:18 +08:00

Implement basic structure for beatmap conversion testing

This commit is contained in:
smoogipoo 2018-03-02 00:07:02 +09:00
parent 99e5eb03f6
commit 797d03a65f
8 changed files with 2080 additions and 3 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(875945)]
public new void Test(int beatmapId)
{
base.Test(beatmapId);
}
protected override ConvertValue CreateConvertValue(HitObject hitObject)
{
var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192);
var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
return new ConvertValue
{
StartTime = hitObject.StartTime,
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
StartX = startPosition.X,
StartY = startPosition.Y,
EndX = endPosition.X,
EndY = endPosition.Y
};
}
protected override ITestableBeatmapConverter CreateConverter() => new OsuBeatmapConverter();
}
public struct ConvertValue : IEquatable<ConvertValue>
{
[JsonProperty]
public double StartTime;
[JsonProperty]
public double EndTime;
[JsonProperty]
public float StartX;
[JsonProperty]
public float StartY;
[JsonProperty]
public float EndX;
[JsonProperty]
public float EndY;
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, 1)
&& Precision.AlmostEquals(EndTime, other.EndTime, 1)
&& Precision.AlmostEquals(StartX, other.StartX, 1)
&& Precision.AlmostEquals(StartY, other.StartY, 1)
&& Precision.AlmostEquals(EndX, other.EndX, 1)
&& Precision.AlmostEquals(EndY, other.EndY, 1);
}
}

View File

@ -37,6 +37,9 @@
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
@ -127,6 +130,7 @@
<Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\OsuBeatmapConversionTest.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCaseHitCircleHidden.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
@ -172,6 +176,10 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Testing\Beatmaps\875945-expected-conversion.json" />
<EmbeddedResource Include="Resources\Testing\Beatmaps\875945.osu" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@ -8,12 +8,36 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
{
public interface ITestableBeatmapConverter
{
/// <summary>
/// Invoked when a <see cref="HitObject"/> has been converted.
/// The first argument contains the <see cref="HitObject"/> that was converted.
/// The second argument contains the <see cref="HitObject"/>s that were output from the conversion process.
/// </summary>
event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
/// <summary>
/// Converts a Beatmap using this Beatmap Converter.
/// </summary>
/// <param name="original">The un-converted Beatmap.</param>
void Convert(Beatmap beatmap);
}
/// <summary>
/// Converts a Beatmap for another mode.
/// </summary>
/// <typeparam name="T">The type of HitObject stored in the Beatmap.</typeparam>
public abstract class BeatmapConverter<T> where T : HitObject
public abstract class BeatmapConverter<T> : ITestableBeatmapConverter
where T : HitObject
{
private event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
event Action<HitObject, IEnumerable<HitObject>> ITestableBeatmapConverter.ObjectConverted
{
add => ObjectConverted += value;
remove => ObjectConverted -= value;
}
/// <summary>
/// Checks if a Beatmap can be converted using this Beatmap Converter.
/// </summary>
@ -32,6 +56,8 @@ namespace osu.Game.Beatmaps
return ConvertBeatmap(new Beatmap(original));
}
void ITestableBeatmapConverter.Convert(Beatmap original) => Convert(original);
/// <summary>
/// Performs the conversion of a Beatmap using this Beatmap Converter.
/// </summary>
@ -63,8 +89,11 @@ namespace osu.Game.Beatmaps
yield break;
}
var converted = ConvertHitObject(original, beatmap).ToList();
ObjectConverted?.Invoke(original, converted);
// Convert the hit object
foreach (var obj in ConvertHitObject(original, beatmap))
foreach (var obj in converted)
{
if (obj == null)
continue;

View File

@ -2,7 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using osu.Framework.Testing;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@ -16,6 +18,9 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("osu.Game.Tests")]
[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)]
// 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.

View File

@ -0,0 +1,139 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public abstract class BeatmapConversionTest<TConvertValue>
where TConvertValue : IEquatable<TConvertValue>
{
private const string resource_namespace = "Testing.Beatmaps";
private const string expected_conversion_suffix = "-expected-conversion";
protected abstract string ResourceAssembly { get; }
protected void Test(int beatmapId)
{
var ourResult = convert(beatmapId);
var expectedResult = read(beatmapId);
Assert.Multiple(() =>
{
int mappingCounter = 0;
while (true)
{
if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count)
break;
if (mappingCounter >= ourResult.Mappings.Count)
Assert.Fail($"Missing conversion for object at time: {expectedResult.Mappings[mappingCounter].StartTime}");
else if (mappingCounter >= expectedResult.Mappings.Count)
Assert.Fail($"Extra conversion for object at time: {ourResult.Mappings[mappingCounter].StartTime}");
else
{
var counter = mappingCounter;
Assert.Multiple(() =>
{
var ourMapping = ourResult.Mappings[counter];
var expectedMapping = expectedResult.Mappings[counter];
int objectCounter = 0;
while (true)
{
if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count)
break;
if (objectCounter >= ourMapping.Objects.Count)
Assert.Fail($"Expected conversion for object at time: {expectedMapping.StartTime}:\n{JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}");
else if (objectCounter >= expectedMapping.Objects.Count)
Assert.Fail($"Unexpected conversion for object at time: {ourMapping.StartTime}:\n{JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}");
else if (!EqualityComparer<TConvertValue>.Default.Equals(expectedMapping.Objects[objectCounter], ourMapping.Objects[objectCounter]))
{
Assert.Fail($"Converted hitobjects differ for object at time: {expectedMapping.StartTime}\n"
+ $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
}
objectCounter++;
}
});
}
mappingCounter++;
}
});
}
private ConvertResult convert(int beatmapId)
{
var beatmap = getBeatmap(beatmapId);
var result = new ConvertResult();
var converter = CreateConverter();
converter.ObjectConverted += (orig, converted) =>
{
converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
var mapping = new ConvertMapping { StartTime = orig.StartTime };
foreach (var obj in converted)
mapping.Objects.Add(CreateConvertValue(obj));
result.Mappings.Add(mapping);
};
converter.Convert(beatmap);
return result;
}
private ConvertResult read(int beatmapId)
{
using (var resStream = openResource($"{resource_namespace}.{beatmapId}{expected_conversion_suffix}.json"))
using (var reader = new StreamReader(resStream))
{
var contents = reader.ReadToEnd();
return JsonConvert.DeserializeObject<ConvertResult>(contents);
}
}
private Beatmap getBeatmap(int beatmapId)
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = openResource($"{resource_namespace}.{beatmapId}.osu"))
using (var stream = new StreamReader(resStream))
return decoder.DecodeBeatmap(stream);
}
private Stream openResource(string name)
{
var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
}
protected abstract TConvertValue CreateConvertValue(HitObject hitObject);
protected abstract ITestableBeatmapConverter CreateConverter();
private class ConvertMapping
{
[JsonProperty]
public double StartTime;
[JsonProperty]
public List<TConvertValue> Objects = new List<TConvertValue>();
}
private class ConvertResult
{
[JsonProperty]
public List<ConvertMapping> Mappings = new List<ConvertMapping>();
}
}
}

View File

@ -883,6 +883,7 @@
<Compile Include="Storyboards\StoryboardLayer.cs" />
<Compile Include="Storyboards\StoryboardSample.cs" />
<Compile Include="Storyboards\StoryboardSprite.cs" />
<Compile Include="Tests\Beatmaps\BeatmapConversionTest.cs" />
<Compile Include="Tests\Beatmaps\TestWorkingBeatmap.cs" />
<Compile Include="Tests\CleanRunHeadlessGameHost.cs" />
<Compile Include="Tests\Platform\TestStorage.cs" />
@ -939,4 +940,4 @@
<Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets')" />
<Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
<Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets')" />
</Project>
</Project>