2018-04-13 17:19:50 +08:00
// 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 ;
2018-05-07 13:04:37 +08:00
using osu.Game.Rulesets ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects ;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
2018-06-14 19:28:29 +08:00
public abstract class BeatmapConversionTest < TConvertMapping , TConvertValue >
where TConvertMapping : ConvertMapping < TConvertValue > , IEquatable < TConvertMapping > , new ( )
2018-04-13 17:19:50 +08:00
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 ( string name )
{
var ourResult = convert ( name ) ;
var expectedResult = read ( name ) ;
Assert . Multiple ( ( ) = >
{
int mappingCounter = 0 ;
while ( true )
{
if ( mappingCounter > = ourResult . Mappings . Count & & mappingCounter > = expectedResult . Mappings . Count )
break ;
if ( mappingCounter > = ourResult . Mappings . Count )
Assert . Fail ( $"A conversion did not generate any hitobjects, but should have, for hitobject at time: {expectedResult.Mappings[mappingCounter].StartTime}\n" ) ;
else if ( mappingCounter > = expectedResult . Mappings . Count )
Assert . Fail ( $"A conversion generated hitobjects, but should not have, for hitobject at time: {ourResult.Mappings[mappingCounter].StartTime}\n" ) ;
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 ( $"The conversion did not generate a hitobject, but should have, for hitobject at time: {expectedMapping.StartTime}:\n"
+ $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n" ) ;
else if ( objectCounter > = expectedMapping . Objects . Count )
Assert . Fail ( $"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n" ) ;
2018-06-14 19:28:29 +08:00
else if ( ! expectedMapping . Equals ( ourMapping ) )
Assert . Fail ( $"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
+ $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
2018-06-14 20:29:08 +08:00
+ $"Received: {JsonConvert.SerializeObject(ourMapping)}\n" ) ;
2018-06-14 19:28:29 +08:00
else if ( ! expectedMapping . Objects [ objectCounter ] . Equals ( ourMapping . Objects [ objectCounter ] ) )
2018-04-13 17:19:50 +08:00
{
2018-06-14 19:28:29 +08:00
Assert . Fail ( $"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n"
2018-04-13 17:19:50 +08:00
+ $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n" ) ;
}
objectCounter + + ;
}
} ) ;
}
mappingCounter + + ;
}
} ) ;
}
private ConvertResult convert ( string name )
{
var beatmap = getBeatmap ( name ) ;
2018-05-16 12:09:48 +08:00
var rulesetInstance = CreateRuleset ( ) ;
2018-05-07 13:04:37 +08:00
beatmap . BeatmapInfo . Ruleset = beatmap . BeatmapInfo . RulesetID = = rulesetInstance . RulesetInfo . ID ? rulesetInstance . RulesetInfo : new RulesetInfo ( ) ;
2018-04-13 17:19:50 +08:00
var result = new ConvertResult ( ) ;
2018-05-16 12:09:48 +08:00
var converter = rulesetInstance . CreateBeatmapConverter ( beatmap ) ;
2018-06-13 17:38:27 +08:00
2018-04-13 17:19:50 +08:00
converter . ObjectConverted + = ( orig , converted ) = >
{
converted . ForEach ( h = > h . ApplyDefaults ( beatmap . ControlPointInfo , beatmap . BeatmapInfo . BaseDifficulty ) ) ;
2018-06-14 19:26:55 +08:00
2018-06-14 19:28:29 +08:00
var mapping = CreateConvertMapping ( ) ;
mapping . StartTime = orig . StartTime ;
2018-06-14 19:26:55 +08:00
foreach ( var obj in converted )
mapping . Objects . AddRange ( CreateConvertValue ( obj ) ) ;
result . Mappings . Add ( mapping ) ;
2018-06-13 17:38:27 +08:00
} ;
IBeatmap convertedBeatmap = converter . Convert ( ) ;
2018-06-13 20:17:27 +08:00
rulesetInstance . CreateBeatmapProcessor ( convertedBeatmap ) ? . PostProcess ( ) ;
2018-04-13 17:19:50 +08:00
return result ;
}
private ConvertResult read ( string name )
{
using ( var resStream = openResource ( $"{resource_namespace}.{name}{expected_conversion_suffix}.json" ) )
using ( var reader = new StreamReader ( resStream ) )
{
var contents = reader . ReadToEnd ( ) ;
return JsonConvert . DeserializeObject < ConvertResult > ( contents ) ;
}
}
2018-04-19 19:44:38 +08:00
private IBeatmap getBeatmap ( string name )
2018-04-13 17:19:50 +08:00
{
using ( var resStream = openResource ( $"{resource_namespace}.{name}.osu" ) )
using ( var stream = new StreamReader ( resStream ) )
{
var decoder = Decoder . GetDecoder < Beatmap > ( stream ) ;
( ( LegacyBeatmapDecoder ) decoder ) . ApplyOffsets = false ;
return decoder . Decode ( 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}" ) ;
}
2018-06-14 19:28:29 +08:00
/// <summary>
/// Creates the conversion mapping for a <see cref="HitObject"/>. A conversion mapping stores important information about the conversion process.
/// This is generated _after_ the <see cref="HitObject"/> has been converted.
/// <para>
/// This should be used to validate the integrity of the conversion process after a conversion has occurred.
/// </para>
/// </summary>
protected virtual TConvertMapping CreateConvertMapping ( ) = > new TConvertMapping ( ) ;
/// <summary>
/// Creates the conversion value for a <see cref="HitObject"/>. A conversion value stores information about the converted <see cref="HitObject"/>.
/// <para>
/// This should be used to validate the integrity of the converted <see cref="HitObject"/>.
/// </para>
/// </summary>
/// <param name="hitObject">The converted <see cref="HitObject"/>.</param>
2018-04-13 17:19:50 +08:00
protected abstract IEnumerable < TConvertValue > CreateConvertValue ( HitObject hitObject ) ;
2018-06-14 19:28:29 +08:00
/// <summary>
/// Creates the <see cref="Ruleset"/> applicable to this <see cref="BeatmapConversionTest{TConvertMapping,TConvertValue}"/>.
/// </summary>
/// <returns></returns>
protected abstract Ruleset CreateRuleset ( ) ;
2018-04-13 17:19:50 +08:00
private class ConvertResult
{
[JsonProperty]
2018-06-14 19:28:29 +08:00
public List < TConvertMapping > Mappings = new List < TConvertMapping > ( ) ;
2018-04-13 17:19:50 +08:00
}
}
2018-06-14 19:28:29 +08:00
public abstract class BeatmapConversionTest < TConvertValue > : BeatmapConversionTest < ConvertMapping < TConvertValue > , TConvertValue >
where TConvertValue : IEquatable < TConvertValue >
{
}
public class ConvertMapping < TConvertValue > : IEquatable < ConvertMapping < TConvertValue > >
where TConvertValue : IEquatable < TConvertValue >
{
[JsonProperty]
public double StartTime ;
[JsonIgnore]
public List < TConvertValue > Objects = new List < TConvertValue > ( ) ;
[JsonProperty("Objects")]
private List < TConvertValue > setObjects { set = > Objects = value ; }
public virtual bool Equals ( ConvertMapping < TConvertValue > other ) = > StartTime . Equals ( other ? . StartTime ) ;
}
2018-04-13 17:19:50 +08:00
}