Merge branch 'master' into keyboard_shortcuts

This commit is contained in:
Dean Herbert
2021-05-27 16:30:34 +09:00
committed by GitHub
1595 changed files with 60121 additions and 14744 deletions

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class BeatmapDifficultyManagerTest
public class BeatmapDifficultyCacheTest
{
[Test]
public void TestKeyEqualsWithDifferentModInstances()

View File

@ -707,6 +707,69 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null));
Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0)));
Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null));
// Last control point duplicated
var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
Assert.That(fourth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
Assert.That(fourth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
Assert.That(fourth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fourth.ControlPoints[1].Type.Value, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
Assert.That(fourth.ControlPoints[2].Type.Value, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fourth.ControlPoints[3].Type.Value, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fourth.ControlPoints[4].Type.Value, Is.EqualTo(null));
// Last control point in segment duplicated
var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
Assert.That(fifth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
Assert.That(fifth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
Assert.That(fifth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fifth.ControlPoints[1].Type.Value, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
Assert.That(fifth.ControlPoints[2].Type.Value, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fifth.ControlPoints[3].Type.Value, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fifth.ControlPoints[4].Type.Value, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(4, 4)));
Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier));
Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5)));
Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null));
// Implicit perfect-curve multi-segment(Should convert to bezier to match stable)
var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier);
Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
Assert.That(sixth.ControlPoints[1].Type.Value == null);
Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier);
Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
Assert.That(sixth.ControlPoints[3].Type.Value == null);
Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
Assert.That(sixth.ControlPoints[4].Type.Value == null);
// Explicit perfect-curve multi-segment(Should not convert to bezier)
var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve);
Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
Assert.That(seventh.ControlPoints[1].Type.Value == null);
Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve);
Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
Assert.That(seventh.ControlPoints[3].Type.Value == null);
Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
Assert.That(seventh.ControlPoints[4].Type.Value == null);
}
}
}

View File

@ -18,10 +18,14 @@ using osu.Game.IO;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Taiko;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Beatmaps.Formats
{
@ -45,6 +49,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
[Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{
var beatmap = new Beatmap
{
HitObjects =
{
new Slider
{
Position = new Vector2(0.6f),
Path = new SliderPath(new[]
{
new PathControlPoint(Vector2.Zero, PathType.Bezier),
new PathControlPoint(new Vector2(0.5f)),
new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int).
new PathControlPoint(new Vector2(1f), PathType.Bezier),
new PathControlPoint(new Vector2(2f))
})
},
}
};
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty);
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual
@ -137,6 +168,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
}

View File

@ -95,6 +95,26 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestOutOfOrderStartTimes()
{
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("out-of-order-starttimes.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
Assert.AreEqual(2, background.Elements.Count);
Assert.AreEqual(1500, background.Elements[0].StartTime);
Assert.AreEqual(1000, background.Elements[1].StartTime);
Assert.AreEqual(1000, storyboard.EarliestEventTime);
}
}
[Test]
public void TestDecodeVariableWithSuffix()
{
@ -109,5 +129,25 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X);
}
}
[Test]
public void TestDecodeOutOfRangeLoopAnimationType()
{
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("animation-types.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[0]).LoopType);
Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[1]).LoopType);
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[2]).LoopType);
Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[3]).LoopType);
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[4]).LoopType);
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType);
}
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
@ -264,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
// change filename
var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}"));
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}"));
using (var zip = ZipArchive.Create())
{
@ -852,6 +853,21 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
public static async Task<BeatmapSetInfo> LoadQuickOszIntoOsu(OsuGameBase osu)
{
var temp = TestResources.GetQuickTestBeatmapForImport();
var manager = osu.Dependencies.Get<BeatmapManager>();
var importedSet = await manager.Import(new ImportTask(temp));
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);

View File

@ -21,6 +21,27 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(36, result.Links[0].Length);
}
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456")]
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456?whatever")]
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123/456")]
[TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc/def")]
[TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123")]
[TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123/whatever")]
[TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc")]
public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link)
{
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
Message result = MessageFormatter.FormatMessage(new Message { Content = link });
Assert.AreEqual(result.Content, result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual(expectedAction, result.Links[0].Action);
Assert.AreEqual(expectedArg, result.Links[0].Argument);
if (expectedAction == LinkAction.External)
Assert.AreEqual(link, result.Links[0].Url);
}
[Test]
public void TestMultipleComplexLinks()
{

View File

@ -0,0 +1,120 @@
// 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.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckAudioQualityTest
{
private CheckAudioQuality check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckAudioQuality();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" }
}
};
}
[Test]
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.AudioFile = null;
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.Track).Returns((Track)null);
Assert.That(check.Run(new BeatmapVerifierContext(beatmap, mock.Object)), Is.Empty);
}
[Test]
public void TestAcceptable()
{
var context = getContext(192);
Assert.That(check.Run(context), Is.Empty);
}
[Test]
public void TestNullBitrate()
{
var context = getContext(null);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
}
[Test]
public void TestZeroBitrate()
{
var context = getContext(0);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
}
[Test]
public void TestTooHighBitrate()
{
var context = getContext(320);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
}
[Test]
public void TestTooLowBitrate()
{
var context = getContext(64);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate);
}
private BeatmapVerifierContext getContext(int? audioBitrate)
{
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(audioBitrate).Object);
}
/// <summary>
/// Returns the mock of the working beatmap with the given audio properties.
/// </summary>
/// <param name="audioBitrate">The bitrate of the audio file the beatmap uses.</param>
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(int? audioBitrate)
{
var mockTrack = new Mock<Track>();
mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate);
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap);
mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object);
return mockWorkingBeatmap;
}
}
}

View File

@ -0,0 +1,136 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Moq;
using NUnit.Framework;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckBackgroundQualityTest
{
private CheckBackgroundQuality check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckBackgroundQuality();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo
{
Hash = "abcdef"
}
}
})
}
}
};
}
[Test]
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.BackgroundFile = null;
var context = getContext(null, System.Array.Empty<byte>());
Assert.That(check.Run(context), Is.Empty);
}
[Test]
public void TestAcceptable()
{
var context = getContext(new Texture(1920, 1080));
Assert.That(check.Run(context), Is.Empty);
}
[Test]
public void TestTooHighResolution()
{
var context = getContext(new Texture(3840, 2160));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution);
}
[Test]
public void TestLowResolution()
{
var context = getContext(new Texture(640, 480));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution);
}
[Test]
public void TestTooLowResolution()
{
var context = getContext(new Texture(100, 100));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution);
}
[Test]
public void TestTooUncompressed()
{
var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]);
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed);
}
private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null)
{
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object);
}
/// <summary>
/// Returns the mock of the working beatmap with the given background and filesize.
/// </summary>
/// <param name="background">The texture of the background.</param>
/// <param name="fileBytes">The bytes that represent the background file.</param>
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null)
{
var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]);
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.Background).Returns(background);
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
return mock;
}
}
}

View File

@ -0,0 +1,194 @@
// 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.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckConcurrentObjectsTest
{
private CheckConcurrentObjects check;
[SetUp]
public void Setup()
{
check = new CheckConcurrentObjects();
}
[Test]
public void TestCirclesSeparate()
{
assertOk(new List<HitObject>
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 150 }
});
}
[Test]
public void TestCirclesConcurrent()
{
assertConcurrentSame(new List<HitObject>
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 100 }
});
}
[Test]
public void TestCirclesAlmostConcurrent()
{
assertConcurrentSame(new List<HitObject>
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 101 }
});
}
[Test]
public void TestSlidersSeparate()
{
assertOk(new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object,
getSliderMock(startTime: 500, endTime: 900.75d).Object
});
}
[Test]
public void TestSlidersConcurrent()
{
assertConcurrentSame(new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object,
getSliderMock(startTime: 300, endTime: 700.75d).Object
});
}
[Test]
public void TestSlidersAlmostConcurrent()
{
assertConcurrentSame(new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object,
getSliderMock(startTime: 402, endTime: 902.75d).Object
});
}
[Test]
public void TestSliderAndCircleConcurrent()
{
assertConcurrentDifferent(new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object,
new HitCircle { StartTime = 300 }
});
}
[Test]
public void TestManyObjectsConcurrent()
{
var hitobjects = new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object,
getSliderMock(startTime: 200, endTime: 500.75d).Object,
new HitCircle { StartTime = 300 }
};
var issues = check.Run(getContext(hitobjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(3));
Assert.That(issues.Where(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent).ToList(), Has.Count.EqualTo(2));
Assert.That(issues.Any(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame));
}
[Test]
public void TestHoldNotesSeparateOnSameColumn()
{
assertOk(new List<HitObject>
{
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
getHoldNoteMock(startTime: 500, endTime: 900.75d, column: 1).Object
});
}
[Test]
public void TestHoldNotesConcurrentOnDifferentColumns()
{
assertOk(new List<HitObject>
{
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 2).Object
});
}
[Test]
public void TestHoldNotesConcurrentOnSameColumn()
{
assertConcurrentSame(new List<HitObject>
{
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 1).Object
});
}
private Mock<Slider> getSliderMock(double startTime, double endTime, int repeats = 0)
{
var mock = new Mock<Slider>();
mock.SetupGet(s => s.StartTime).Returns(startTime);
mock.As<IHasRepeats>().Setup(r => r.RepeatCount).Returns(repeats);
mock.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
return mock;
}
private Mock<HoldNote> getHoldNoteMock(double startTime, double endTime, int column)
{
var mock = new Mock<HoldNote>();
mock.SetupGet(s => s.StartTime).Returns(startTime);
mock.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
mock.As<IHasColumn>().Setup(c => c.Column).Returns(column);
return mock;
}
private void assertOk(List<HitObject> hitobjects)
{
Assert.That(check.Run(getContext(hitobjects)), Is.Empty);
}
private void assertConcurrentSame(List<HitObject> hitobjects, int count = 1)
{
var issues = check.Run(getContext(hitobjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(count));
Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame));
}
private void assertConcurrentDifferent(List<HitObject> hitobjects, int count = 1)
{
var issues = check.Run(getContext(hitobjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(count));
Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent));
}
private BeatmapVerifierContext getContext(List<HitObject> hitobjects)
{
var beatmap = new Beatmap<HitObject> { HitObjects = hitobjects };
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
}
}
}

View File

@ -0,0 +1,77 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckFilePresenceTest
{
private CheckBackgroundPresence check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckBackgroundPresence();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo { Hash = "abcdef" }
}
})
}
}
};
}
[Test]
public void TestBackgroundSetAndInFiles()
{
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
Assert.That(check.Run(context), Is.Empty);
}
[Test]
public void TestBackgroundSetAndNotInFiles()
{
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist);
}
[Test]
public void TestBackgroundNotSet()
{
beatmap.Metadata.BackgroundFile = null;
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet);
}
}
}

View File

@ -0,0 +1,159 @@
// 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.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckUnsnappedObjectsTest
{
private CheckUnsnappedObjects check;
private ControlPointInfo cpi;
[SetUp]
public void Setup()
{
check = new CheckUnsnappedObjects();
cpi = new ControlPointInfo();
cpi.Add(100, new TimingControlPoint { BeatLength = 100 });
}
[Test]
public void TestCircleSnapped()
{
assertOk(new List<HitObject>
{
new HitCircle { StartTime = 100 }
});
}
[Test]
public void TestCircleUnsnapped1Ms()
{
assert1Ms(new List<HitObject>
{
new HitCircle { StartTime = 101 }
});
assert1Ms(new List<HitObject>
{
new HitCircle { StartTime = 99 }
});
}
[Test]
public void TestCircleUnsnapped2Ms()
{
assert2Ms(new List<HitObject>
{
new HitCircle { StartTime = 102 }
});
assert2Ms(new List<HitObject>
{
new HitCircle { StartTime = 98 }
});
}
[Test]
public void TestSliderSnapped()
{
// Slider ends are naturally < 1 ms unsnapped because of how SV works.
assertOk(new List<HitObject>
{
getSliderMock(startTime: 100, endTime: 400.75d).Object
});
}
[Test]
public void TestSliderUnsnapped1Ms()
{
assert1Ms(new List<HitObject>
{
getSliderMock(startTime: 101, endTime: 401.75d).Object
}, count: 2);
// End is only off by 0.25 ms, hence count 1.
assert1Ms(new List<HitObject>
{
getSliderMock(startTime: 99, endTime: 399.75d).Object
}, count: 1);
}
[Test]
public void TestSliderUnsnapped2Ms()
{
assert2Ms(new List<HitObject>
{
getSliderMock(startTime: 102, endTime: 402.75d).Object
}, count: 2);
// Start and end are 2 ms and 1.25 ms off respectively, hence two different issues in one object.
var hitObjects = new List<HitObject>
{
getSliderMock(startTime: 98, endTime: 398.75d).Object
};
var issues = check.Run(getContext(hitObjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(2));
Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap));
Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap));
}
private Mock<Slider> getSliderMock(double startTime, double endTime, int repeats = 0)
{
var mockSlider = new Mock<Slider>();
mockSlider.SetupGet(s => s.StartTime).Returns(startTime);
mockSlider.As<IHasRepeats>().Setup(r => r.RepeatCount).Returns(repeats);
mockSlider.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
return mockSlider;
}
private void assertOk(List<HitObject> hitObjects)
{
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
}
private void assert1Ms(List<HitObject> hitObjects, int count = 1)
{
var issues = check.Run(getContext(hitObjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(count));
Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap));
}
private void assert2Ms(List<HitObject> hitObjects, int count = 1)
{
var issues = check.Run(getContext(hitObjects)).ToList();
Assert.That(issues, Has.Count.EqualTo(count));
Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap));
}
private BeatmapVerifierContext getContext(List<HitObject> hitObjects)
{
var beatmap = new Beatmap<HitObject>
{
ControlPointInfo = cpi,
HitObjects = hitObjects
};
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
}
}
}

View File

@ -0,0 +1,175 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Editing
{
[HeadlessTest]
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
{
private readonly TestHitObject testObj = new TestHitObject();
private TestPlayfield playfield1;
private TestPlayfield playfield2;
private TestDrawable intermediateDrawable;
private HitObjectUsageEventBuffer eventBuffer;
private HitObject beganUsage;
private HitObject finishedUsage;
private HitObject transferredUsage;
[SetUp]
public void Setup() => Schedule(() =>
{
reset();
if (eventBuffer != null)
{
eventBuffer.HitObjectUsageBegan -= onHitObjectUsageBegan;
eventBuffer.HitObjectUsageFinished -= onHitObjectUsageFinished;
eventBuffer.HitObjectUsageTransferred -= onHitObjectUsageTransferred;
}
var topPlayfield = new TestPlayfield();
topPlayfield.AddNested(playfield1 = new TestPlayfield());
topPlayfield.AddNested(playfield2 = new TestPlayfield());
eventBuffer = new HitObjectUsageEventBuffer(topPlayfield);
eventBuffer.HitObjectUsageBegan += onHitObjectUsageBegan;
eventBuffer.HitObjectUsageFinished += onHitObjectUsageFinished;
eventBuffer.HitObjectUsageTransferred += onHitObjectUsageTransferred;
Children = new Drawable[]
{
topPlayfield,
intermediateDrawable = new TestDrawable(),
};
});
private void onHitObjectUsageBegan(HitObject obj) => beganUsage = obj;
private void onHitObjectUsageFinished(HitObject obj) => finishedUsage = obj;
private void onHitObjectUsageTransferred(HitObject obj, DrawableHitObject drawableObj) => transferredUsage = obj;
[Test]
public void TestUsageBeganAfterAdd()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addCheckStep(began: true);
}
[Test]
public void TestUsageFinishedAfterRemove()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("remove hitobject", () => playfield1.Remove(testObj));
addCheckStep(finished: true);
}
[Test]
public void TestUsageTransferredWhenMovedBetweenPlayfields()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("transfer hitobject to other playfield", () =>
{
playfield1.Remove(testObj);
playfield2.Add(testObj);
});
addCheckStep(transferred: true);
}
[Test]
public void TestRemoveImmediatelyAfterUsageBegan()
{
AddStep("add hitobject and schedule removal", () =>
{
playfield1.Add(testObj);
intermediateDrawable.Schedule(() => playfield1.Remove(testObj));
});
addCheckStep(began: true, finished: true);
}
[Test]
public void TestRemoveImmediatelyAfterTransferred()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("transfer hitobject to other playfield and schedule removal", () =>
{
playfield1.Remove(testObj);
playfield2.Add(testObj);
intermediateDrawable.Schedule(() => playfield2.Remove(testObj));
});
addCheckStep(transferred: true, finished: true);
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
eventBuffer.Update();
}
private void addResetStep() => AddStep("reset", reset);
private void reset()
{
beganUsage = null;
finishedUsage = null;
transferredUsage = null;
}
private void addCheckStep(bool began = false, bool finished = false, bool transferred = false)
=> AddAssert($"began = {began}, finished = {finished}, transferred = {transferred}",
() => (beganUsage == testObj) == began && (finishedUsage == testObj) == finished && (transferredUsage == testObj) == transferred);
private class TestPlayfield : Playfield
{
public TestPlayfield()
{
RegisterPool<TestHitObject, TestDrawableHitObject>(1);
}
public new void AddNested(Playfield playfield)
{
AddInternal(playfield);
base.AddNested(playfield);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
{
var entry = base.CreateLifetimeEntry(hitObject);
entry.KeepAlive = true;
return entry;
}
}
private class TestHitObject : HitObject
{
public override string ToString() => "TestHitObject";
}
private class TestDrawableHitObject : DrawableHitObject
{
}
private class TestDrawable : Drawable
{
public new void Schedule(Action action) => base.Schedule(action);
}
}
}

View File

@ -0,0 +1,95 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneDrawableHitObject : OsuTestScene
{
[Test]
public void TestEntryLifetime()
{
TestDrawableHitObject dho = null;
var initialHitObject = new HitObject
{
StartTime = 1000
};
var entry = new TestLifetimeEntry(new HitObject
{
StartTime = 2000
});
AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject(initialHitObject));
AddAssert("Correct initial lifetime", () => dho.LifetimeStart == initialHitObject.StartTime - TestDrawableHitObject.INITIAL_LIFETIME_OFFSET);
AddStep("Apply entry", () => dho.Apply(entry));
AddAssert("Correct initial lifetime", () => dho.LifetimeStart == entry.HitObject.StartTime - TestLifetimeEntry.INITIAL_LIFETIME_OFFSET);
AddStep("Set lifetime", () => dho.LifetimeEnd = 3000);
AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000);
}
[Test]
public void TestKeepAlive()
{
TestDrawableHitObject dho = null;
TestLifetimeEntry entry = null;
AddStep("Create DHO", () =>
{
dho = new TestDrawableHitObject(null);
dho.Apply(entry = new TestLifetimeEntry(new HitObject()));
Child = dho;
});
AddStep("KeepAlive = true", () =>
{
entry.LifetimeStart = 0;
entry.LifetimeEnd = 1000;
entry.KeepAlive = true;
});
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
AddStep("KeepAlive = false", () => entry.KeepAlive = false);
AddAssert("Lifetime is correct", () => entry.LifetimeStart == 500 && entry.LifetimeEnd == 1000);
AddStep("Set LifetimeStart while KeepAlive", () =>
{
entry.KeepAlive = true;
dho.LifetimeStart = double.MinValue;
entry.KeepAlive = false;
});
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
}
private class TestDrawableHitObject : DrawableHitObject
{
public const double INITIAL_LIFETIME_OFFSET = 100;
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
public TestDrawableHitObject(HitObject hitObject)
: base(hitObject)
{
}
}
private class TestLifetimeEntry : HitObjectLifetimeEntry
{
public const double INITIAL_LIFETIME_OFFSET = 200;
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
public TestLifetimeEntry(HitObject hitObject)
: base(hitObject)
{
}
}
}
}

View File

@ -1,32 +0,0 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneGameplayClockContainer : OsuTestScene
{
[Test]
public void TestStartThenElapsedTime()
{
GameplayClockContainer gcc = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gcc = new GameplayClockContainer(working, 0));
});
AddStep("start track", () => gcc.Start());
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
}
}
}

View File

@ -121,7 +121,7 @@ namespace osu.Game.Tests.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{

View File

@ -0,0 +1,58 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneMasterGameplayClockContainer : OsuTestScene
{
[Test]
public void TestStartThenElapsedTime()
{
GameplayClockContainer gcc = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gcc = new MasterGameplayClockContainer(working, 0));
});
AddStep("start clock", () => gcc.Start());
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
}
[Test]
public void TestElapseThenReset()
{
GameplayClockContainer gcc = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gcc = new MasterGameplayClockContainer(working, 0));
});
AddStep("start clock", () => gcc.Start());
AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000);
double timeAtReset = 0;
AddStep("reset clock", () =>
{
timeAtReset = gcc.GameplayClock.CurrentTime;
gcc.Reset();
});
AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset);
}
}
}

View File

@ -0,0 +1,85 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneProxyContainer : OsuTestScene
{
private HitObjectContainer hitObjectContainer;
private ProxyContainer proxyContainer;
private readonly ManualClock clock = new ManualClock();
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = new Container
{
Children = new Drawable[]
{
hitObjectContainer = new HitObjectContainer(),
proxyContainer = new ProxyContainer()
},
Clock = new FramedClock(clock)
};
clock.CurrentTime = 0;
});
[Test]
public void TestProxyLifetimeManagement()
{
AddStep("Add proxy drawables", () =>
{
addProxy(new TestDrawableHitObject(1000));
addProxy(new TestDrawableHitObject(3000));
addProxy(new TestDrawableHitObject(5000));
});
AddStep("time = 1000", () => clock.CurrentTime = 1000);
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
AddStep("time = 5000", () => clock.CurrentTime = 5000);
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
AddStep("time = 6000", () => clock.CurrentTime = 6000);
AddAssert("No proxy is alive", () => proxyContainer.AliveChildren.Count == 0);
}
private void addProxy(DrawableHitObject drawableHitObject)
{
hitObjectContainer.Add(drawableHitObject);
proxyContainer.AddProxy(drawableHitObject);
}
private class ProxyContainer : LifetimeManagementContainer
{
public IReadOnlyList<Drawable> AliveChildren => AliveInternalChildren;
public void AddProxy(Drawable d) => AddInternal(d.CreateProxy());
}
private class TestDrawableHitObject : DrawableHitObject
{
protected override double InitialLifetimeOffset => 100;
public TestDrawableHitObject(double startTime)
: base(new HitObject { StartTime = startTime })
{
}
protected override void UpdateInitialTransforms()
{
LifetimeEnd = LifetimeStart + 500;
}
}
}
}

View File

@ -10,13 +10,17 @@ using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Storyboards;
@ -27,15 +31,15 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneStoryboardSamples : OsuTestScene
public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
{
[Test]
public void TestRetrieveTopLevelSample()
{
ISkin skin = null;
SampleChannel channel = null;
ISample channel = null;
AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
AddStep("create skin", () => skin = new TestSkin("test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
AddAssert("sample is non-null", () => channel != null);
@ -45,9 +49,9 @@ namespace osu.Game.Tests.Gameplay
public void TestRetrieveSampleInSubFolder()
{
ISkin skin = null;
SampleChannel channel = null;
ISample channel = null;
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
AddAssert("sample is non-null", () => channel != null);
@ -64,17 +68,47 @@ namespace osu.Game.Tests.Gameplay
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gameplayContainer = new GameplayClockContainer(working, 0));
gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)
{
Clock = gameplayContainer.GameplayClock
IsPaused = { Value = true },
Child = new FrameStabilityContainer
{
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
}
});
});
AddStep("reset clock", () => gameplayContainer.Start());
AddUntilStep("sample played", () => sample.RequestedPlaying);
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
}
[Test]
public void TestSampleHasLifetimeEndWithInitialClockTime()
{
GameplayClockContainer gameplayContainer = null;
DrawableStoryboardSample sample = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true)
{
IsPaused = { Value = true },
Child = new FrameStabilityContainer
{
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
}
});
});
AddStep("start time", () => gameplayContainer.Start());
AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue);
AddUntilStep("sample not played", () => !sample.RequestedPlaying);
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
}
[TestCase(typeof(OsuModDoubleTime), 1.5)]
@ -88,6 +122,7 @@ namespace osu.Game.Tests.Gameplay
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
{
GameplayClockContainer gameplayContainer = null;
StoryboardSampleInfo sampleInfo = null;
TestDrawableStoryboardSample sample = null;
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
@ -99,23 +134,23 @@ namespace osu.Game.Tests.Gameplay
break;
case ModTimeRamp m:
m.InitialRate.Value = m.FinalRate.Value = expectedRate;
m.FinalRate.Value = m.InitialRate.Value = expectedRate;
break;
}
AddStep("setup storyboard sample", () =>
{
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
SelectedMods.Value = new[] { testedMod };
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)
Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
{
Child = beatmapSkinSourceContainer
});
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
});
@ -123,13 +158,16 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start());
AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate);
AddAssert("sample playback rate matches mod rates", () =>
testedMod != null && Precision.AlmostEquals(
sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value,
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
}
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, AudioManager audioManager)
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
public TestSkin(string resourceName, IStorageResourceProvider resources)
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini")
{
}
}
@ -158,15 +196,15 @@ namespace osu.Game.Tests.Gameplay
private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{
private readonly AudioManager audio;
private readonly IStorageResourceProvider resources;
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio)
: base(ruleset, null, audio)
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, IStorageResourceProvider resources)
: base(ruleset, null, resources.AudioManager)
{
this.audio = audio;
this.resources = resources;
}
protected override ISkin GetSkin() => new TestSkin("test-sample", audio);
protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
}
private class TestDrawableStoryboardSample : DrawableStoryboardSample
@ -176,5 +214,13 @@ namespace osu.Game.Tests.Gameplay
{
}
}
#region IResourceStorageProvider
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
#endregion
}
}

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tests.Input
=> AddStep($"make window {mode}", () => frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode).Value = mode);
private void setGameSideModeTo(OsuConfineMouseMode mode)
=> AddStep($"set {mode} game-side", () => Game.LocalConfig.Set(OsuSetting.ConfineMouseMode, mode));
=> AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode));
private void setLocalUserPlayingTo(bool playing)
=> AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing);

View File

@ -0,0 +1,36 @@
// 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 NUnit.Framework;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Mods
{
[TestFixture]
public class ModSettingsEqualityComparison
{
[Test]
public void Test()
{
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var apiMod1 = new APIMod(mod1);
var apiMod2 = new APIMod(mod2);
var apiMod3 = new APIMod(mod3);
Assert.That(mod1, Is.Not.EqualTo(mod2));
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
Assert.That(mod2, Is.EqualTo(mod2));
Assert.That(apiMod2, Is.EqualTo(apiMod2));
Assert.That(mod2, Is.EqualTo(mod3));
Assert.That(apiMod2, Is.EqualTo(apiMod3));
Assert.That(mod3, Is.EqualTo(mod2));
Assert.That(apiMod3, Is.EqualTo(apiMod2));
}
}
}

View File

@ -0,0 +1,160 @@
// 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.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Utils;
namespace osu.Game.Tests.Mods
{
[TestFixture]
public class ModUtilsTest
{
[Test]
public void TestModIsCompatibleByItself()
{
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object }));
}
[Test]
public void TestIncompatibleThroughTopLevel()
{
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False);
}
[Test]
public void TestMultiModIncompatibleWithTopLevel()
{
var mod1 = new Mock<CustomMod1>();
// The nested mod.
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() });
var multiMod = new MultiMod(new MultiMod(mod2.Object));
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod1.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, multiMod }), Is.False);
}
[Test]
public void TestTopLevelIncompatibleWithMultiMod()
{
// The nested mod.
var mod1 = new Mock<CustomMod1>();
var multiMod = new MultiMod(new MultiMod(mod1.Object));
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(CustomMod1) });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, multiMod }), Is.False);
}
[Test]
public void TestCompatibleMods()
{
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.True);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.True);
}
[Test]
public void TestIncompatibleThroughBaseType()
{
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(Mod) });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False);
}
[Test]
public void TestAllowedThroughMostDerivedType()
{
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() }));
}
[Test]
public void TestNotAllowedThroughBaseType()
{
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False);
}
private static readonly object[] invalid_mod_test_scenarios =
{
// incompatible pair.
new object[]
{
new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() },
new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) }
},
// incompatible pair with derived class.
new object[]
{
new Mod[] { new OsuModNightcore(), new OsuModHalfTime() },
new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) }
},
// system mod.
new object[]
{
new Mod[] { new OsuModDoubleTime(), new OsuModTouchDevice() },
new[] { typeof(OsuModTouchDevice) }
},
// multi mod.
new object[]
{
new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModHalfTime() },
new[] { typeof(MultiMod) }
},
// valid pair.
new object[]
{
new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() },
null
}
};
[TestCaseSource(nameof(invalid_mod_test_scenarios))]
public void TestInvalidModScenarios(Mod[] inputMods, Type[] expectedInvalid)
{
bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid);
Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
if (isValid)
Assert.IsNull(invalid);
else
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
}
public abstract class CustomMod1 : Mod
{
}
public abstract class CustomMod2 : Mod
{
}
}
}

View File

@ -0,0 +1,73 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
namespace osu.Game.Tests.Mods
{
[TestFixture]
public class SettingsSourceAttributeTest
{
[Test]
public void TestOrdering()
{
var objectWithSettings = new ClassWithSettings();
var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray();
Assert.That(orderedSettings, Has.Length.EqualTo(4));
Assert.That(orderedSettings[0].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.FirstSetting)));
Assert.That(orderedSettings[1].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.SecondSetting)));
Assert.That(orderedSettings[2].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.ThirdSetting)));
Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting)));
}
[Test]
public void TestCustomControl()
{
var objectWithCustomSettingControl = new ClassWithCustomSettingControl();
var settings = objectWithCustomSettingControl.CreateSettingsControls().ToArray();
Assert.That(settings, Has.Length.EqualTo(1));
Assert.That(settings[0], Is.TypeOf<CustomSettingsControl>());
}
private class ClassWithSettings
{
[SettingSource("Unordered setting", "Should be last")]
public BindableFloat UnorderedSetting { get; set; } = new BindableFloat();
[SettingSource("Second setting", "Another description", 2)]
public BindableBool SecondSetting { get; set; } = new BindableBool();
[SettingSource("First setting", "A description", 1)]
public BindableDouble FirstSetting { get; set; } = new BindableDouble();
[SettingSource("Third setting", "Yet another description", 3)]
public BindableInt ThirdSetting { get; set; } = new BindableInt();
}
private class ClassWithCustomSettingControl
{
[SettingSource("Custom setting", "Should be a custom control", SettingControlType = typeof(CustomSettingsControl))]
public BindableInt UnorderedSetting { get; set; } = new BindableInt();
}
private class CustomSettingsControl : SettingsItem<int>
{
protected override Drawable CreateControl() => new CustomControl();
private class CustomControl : Drawable, IHasCurrentValue<int>
{
public Bindable<int> Current { get; set; } = new Bindable<int>();
}
}
}
}

View File

@ -0,0 +1,91 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.NonVisual
{
public class ClosestBeatDivisorTest
{
[Test]
public void TestExactDivisors()
{
var cpi = new ControlPointInfo();
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
assertClosestDivisors(divisors, divisors, cpi);
}
[Test]
public void TestExactDivisorWithTempoChanges()
{
int offset = 0;
int[] beatLengths = { 1000, 200, 100, 50 };
var cpi = new ControlPointInfo();
foreach (int beatLength in beatLengths)
{
cpi.Add(offset, new TimingControlPoint { BeatLength = beatLength });
offset += beatLength * 2;
}
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3 };
assertClosestDivisors(divisors, divisors, cpi);
}
[Test]
public void TestExactDivisorsHighBPMStream()
{
var cpi = new ControlPointInfo();
cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
// A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors.
double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 };
double[] closestDivisors = { 4, 2, 4, 1, 4, 2, 4, 1 };
assertClosestDivisors(divisors, closestDivisors, cpi, step: 1 / 4d);
}
[Test]
public void TestApproximateDivisors()
{
var cpi = new ControlPointInfo();
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 };
double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
assertClosestDivisors(divisors, closestDivisors, cpi);
}
private void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1)
{
List<HitObject> hitobjects = new List<HitObject>();
double offset = cpi.TimingPoints[0].Time;
for (int i = 0; i < divisors.Count; ++i)
{
double beatLength = cpi.TimingPointAt(offset).BeatLength;
hitobjects.Add(new HitObject { StartTime = offset + beatLength / divisors[i] });
offset += beatLength * step;
}
var beatmap = new Beatmap
{
HitObjects = hitobjects,
ControlPointInfo = cpi
};
for (int i = 0; i < divisors.Count; ++i)
Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.GetClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}");
}
}
}

View File

@ -246,5 +246,32 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
}
[Test]
public void TestCreateCopyIsDeepClone()
{
var cpi = new ControlPointInfo();
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
var cpiCopy = cpi.CreateCopy();
cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 });
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpiCopy.Groups.Count, Is.EqualTo(2));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
Assert.That(cpiCopy.TimingPoints.Count, Is.EqualTo(2));
Assert.That(cpi.TimingPoints[0], Is.Not.SameAs(cpiCopy.TimingPoints[0]));
Assert.That(cpi.TimingPoints[0].BeatLengthBindable, Is.Not.SameAs(cpiCopy.TimingPoints[0].BeatLengthBindable));
Assert.That(cpi.TimingPoints[0].BeatLength, Is.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
cpi.TimingPoints[0].BeatLength = 800;
Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
}
}
}

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost())
{
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
storageConfig.Set(StorageConfig.FullPath, customPath);
storageConfig.SetValue(StorageConfig.FullPath, customPath);
try
{
@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost())
{
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
storageConfig.Set(StorageConfig.FullPath, customPath);
storageConfig.SetValue(StorageConfig.FullPath, customPath);
try
{

View File

@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual
{
public override string Name => nameof(ModA);
public override string Acronym => nameof(ModA);
public override string Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual
private class ModB : Mod
{
public override string Name => nameof(ModB);
public override string Description => string.Empty;
public override string Acronym => nameof(ModB);
public override double ScoreMultiplier => 1;
@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual
{
public override string Name => nameof(ModC);
public override string Acronym => nameof(ModC);
public override string Description => string.Empty;
public override double ScoreMultiplier => 1;
}
@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual
{
public override string Name => $"Incompatible With {nameof(ModA)}";
public override string Acronym => $"Incompatible With {nameof(ModA)}";
public override string Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual
{
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
@ -212,7 +217,7 @@ namespace osu.Game.Tests.NonVisual
throw new NotImplementedException();
}
protected override Skill[] CreateSkills(IBeatmap beatmap)
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{
throw new NotImplementedException();
}

View File

@ -4,8 +4,10 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering
{
@ -214,5 +216,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
[Test]
public void TestCustomRulesetCriteria([Values(null, true, false)] bool? matchCustomCriteria)
{
var beatmap = getExampleBeatmap();
var customCriteria = matchCustomCriteria is bool match ? new CustomCriteria(match) : null;
var criteria = new FilterCriteria { RulesetCriteria = customCriteria };
var carouselItem = new CarouselBeatmap(beatmap);
carouselItem.Filter(criteria);
Assert.AreEqual(matchCustomCriteria == false, carouselItem.Filtered.Value);
}
private class CustomCriteria : IRulesetFilterCriteria
{
private readonly bool match;
public CustomCriteria(bool shouldMatch)
{
match = shouldMatch;
}
public bool Matches(BeatmapInfo beatmap) => match;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false;
}
}
}

View File

@ -4,7 +4,9 @@
using System;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering
{
@ -194,5 +196,63 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
}
[Test]
public void TestOperatorParsing()
{
const string query = "artist=><something";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("><something", filterCriteria.Artist.SearchTerm);
}
[Test]
public void TestUnrecognisedKeywordIsIgnored()
{
const string query = "unrecognised=keyword";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText);
}
[TestCase("cs=nope")]
[TestCase("bpm>=bad")]
[TestCase("divisor<nah")]
[TestCase("status=noidea")]
public void TestInvalidKeywordValueIsIgnored(string query)
{
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual(query, filterCriteria.SearchText);
}
[Test]
public void TestCustomKeywordIsParsed()
{
var customCriteria = new CustomFilterCriteria();
const string query = "custom=readme unrecognised=keyword";
var filterCriteria = new FilterCriteria { RulesetCriteria = customCriteria };
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("readme", customCriteria.CustomValue);
Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText.Trim());
}
private class CustomFilterCriteria : IRulesetFilterCriteria
{
public string CustomValue { get; set; }
public bool Matches(BeatmapInfo beatmap) => true;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
{
if (key == "custom" && op == Operator.Equal)
{
CustomValue = value;
return true;
}
return false;
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Globalization;
using NUnit.Framework;
using osu.Game.Utils;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class FormatUtilsTest
{
[TestCase(0, "0.00%")]
[TestCase(0.01, "1.00%")]
[TestCase(0.9899, "98.99%")]
[TestCase(0.989999, "98.99%")]
[TestCase(0.99, "99.00%")]
[TestCase(0.9999, "99.99%")]
[TestCase(0.999999, "99.99%")]
[TestCase(1, "100.00%")]
public void TestAccuracyFormatting(double input, string expectedOutput)
{
Assert.AreEqual(expectedOutput, input.FormatAccuracy(CultureInfo.InvariantCulture));
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
@ -20,27 +21,14 @@ namespace osu.Game.Tests.NonVisual
{
handler = new TestInputHandler(replay = new Replay
{
Frames = new List<ReplayFrame>
{
new TestReplayFrame(0),
new TestReplayFrame(1000),
new TestReplayFrame(2000),
new TestReplayFrame(3000, true),
new TestReplayFrame(4000, true),
new TestReplayFrame(5000, true),
new TestReplayFrame(7000, true),
new TestReplayFrame(8000),
}
HasReceivedAllFrames = false
});
}
[Test]
public void TestNormalPlayback()
{
Assert.IsNull(handler.CurrentFrame);
confirmCurrentFrame(null);
confirmNextFrame(0);
setReplayFrames();
setTime(0, 0);
confirmCurrentFrame(0);
@ -107,6 +95,8 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestIntroTime()
{
setReplayFrames();
setTime(-1000, -1000);
confirmCurrentFrame(null);
confirmNextFrame(0);
@ -123,6 +113,8 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestBasicRewind()
{
setReplayFrames();
setTime(2800, 0);
setTime(2800, 1000);
setTime(2800, 2000);
@ -133,34 +125,35 @@ namespace osu.Game.Tests.NonVisual
// pivot without crossing a frame boundary
setTime(2700, 2700);
confirmCurrentFrame(2);
confirmNextFrame(1);
confirmNextFrame(3);
// cross current frame boundary; should not yet update frame
setTime(1980, 1980);
// cross current frame boundary
setTime(1980, 2000);
confirmCurrentFrame(2);
confirmNextFrame(1);
confirmNextFrame(3);
setTime(1200, 1200);
confirmCurrentFrame(2);
confirmNextFrame(1);
confirmCurrentFrame(1);
confirmNextFrame(2);
// ensure each frame plays out until start
setTime(-500, 1000);
confirmCurrentFrame(1);
confirmNextFrame(0);
confirmNextFrame(2);
setTime(-500, 0);
confirmCurrentFrame(0);
confirmNextFrame(null);
confirmNextFrame(1);
setTime(-500, -500);
confirmCurrentFrame(0);
confirmNextFrame(null);
confirmCurrentFrame(null);
confirmNextFrame(0);
}
[Test]
public void TestRewindInsideImportantSection()
{
setReplayFrames();
fastForwardToPoint(3000);
setTime(4000, 4000);
@ -168,12 +161,12 @@ namespace osu.Game.Tests.NonVisual
confirmNextFrame(5);
setTime(3500, null);
confirmCurrentFrame(4);
confirmNextFrame(3);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3000, 3000);
confirmCurrentFrame(3);
confirmNextFrame(2);
confirmNextFrame(4);
setTime(3500, null);
confirmCurrentFrame(3);
@ -187,46 +180,175 @@ namespace osu.Game.Tests.NonVisual
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(4000, null);
setTime(4000, 4000);
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(3500, null);
confirmCurrentFrame(4);
confirmNextFrame(3);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3000, 3000);
confirmCurrentFrame(3);
confirmNextFrame(2);
confirmNextFrame(4);
}
[Test]
public void TestRewindOutOfImportantSection()
{
setReplayFrames();
fastForwardToPoint(3500);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3200, null);
// next frame doesn't change even though direction reversed, because of important section.
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3000, null);
setTime(3000, 3000);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(2800, 2800);
confirmCurrentFrame(3);
confirmNextFrame(2);
confirmCurrentFrame(2);
confirmNextFrame(3);
}
[Test]
public void TestReplayStreaming()
{
// no frames are arrived yet
setTime(0, null);
setTime(1000, null);
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting for the first frame");
replay.Frames.Add(new TestReplayFrame(0));
replay.Frames.Add(new TestReplayFrame(1000));
// should always play from beginning
setTime(1000, 0);
confirmCurrentFrame(0);
Assert.IsFalse(handler.WaitingForFrame, "Should not be waiting yet");
setTime(1000, 1000);
confirmCurrentFrame(1);
confirmNextFrame(null);
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting");
// cannot seek beyond the last frame
setTime(1500, null);
confirmCurrentFrame(1);
setTime(-100, 0);
confirmCurrentFrame(0);
// can seek to the point before the first frame, however
setTime(-100, -100);
confirmCurrentFrame(null);
confirmNextFrame(0);
fastForwardToPoint(1000);
setTime(3000, null);
replay.Frames.Add(new TestReplayFrame(2000));
confirmCurrentFrame(1);
setTime(1000, 1000);
setTime(3000, 2000);
}
[Test]
public void TestMultipleFramesSameTime()
{
replay.Frames.Add(new TestReplayFrame(0));
replay.Frames.Add(new TestReplayFrame(0));
replay.Frames.Add(new TestReplayFrame(1000));
replay.Frames.Add(new TestReplayFrame(1000));
replay.Frames.Add(new TestReplayFrame(2000));
// forward direction is prioritized when multiple frames have the same time.
setTime(0, 0);
setTime(0, 0);
setTime(2000, 1000);
setTime(2000, 1000);
setTime(1000, 1000);
setTime(1000, 1000);
setTime(-100, 1000);
setTime(-100, 0);
setTime(-100, 0);
setTime(-100, -100);
}
[Test]
public void TestReplayFramesSortStability()
{
const double repeating_time = 5000;
// add a collection of frames in shuffled order time-wise; each frame also stores its original index to check stability later.
// data is hand-picked and breaks if the unstable List<T>.Sort() is used.
// in theory this can still return a false-positive with another unstable algorithm if extremely unlucky,
// but there is no conceivable fool-proof way to prevent that anyways.
replay.Frames.AddRange(new[]
{
repeating_time,
0,
3000,
repeating_time,
repeating_time,
6000,
9000,
repeating_time,
repeating_time,
1000,
11000,
21000,
4000,
repeating_time,
repeating_time,
8000,
2000,
7000,
repeating_time,
repeating_time,
10000
}.Select((time, index) => new TestReplayFrame(time, true, index)));
replay.HasReceivedAllFrames = true;
// create a new handler with the replay for the sort to be performed.
handler = new TestInputHandler(replay);
// ensure sort stability by checking that the frames with time == repeating_time are sorted in ascending frame index order themselves.
var repeatingTimeFramesData = replay.Frames
.Cast<TestReplayFrame>()
.Where(f => f.Time == repeating_time)
.Select(f => f.FrameIndex);
Assert.That(repeatingTimeFramesData, Is.Ordered.Ascending);
}
private void setReplayFrames()
{
replay.Frames = new List<ReplayFrame>
{
new TestReplayFrame(0),
new TestReplayFrame(1000),
new TestReplayFrame(2000),
new TestReplayFrame(3000, true),
new TestReplayFrame(4000, true),
new TestReplayFrame(5000, true),
new TestReplayFrame(7000, true),
new TestReplayFrame(8000),
};
replay.HasReceivedAllFrames = true;
}
private void fastForwardToPoint(double destination)
{
for (int i = 0; i < 1000; i++)
{
if (handler.SetFrameFromTime(destination) == null)
var time = handler.SetFrameFromTime(destination);
if (time == null || time == destination)
return;
}
@ -235,43 +357,29 @@ namespace osu.Game.Tests.NonVisual
private void setTime(double set, double? expect)
{
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
Assert.AreEqual(expect, handler.SetFrameFromTime(set), "Unexpected return value");
}
private void confirmCurrentFrame(int? frame)
{
if (frame.HasValue)
{
Assert.IsNotNull(handler.CurrentFrame);
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
}
else
{
Assert.IsNull(handler.CurrentFrame);
}
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame");
}
private void confirmNextFrame(int? frame)
{
if (frame.HasValue)
{
Assert.IsNotNull(handler.NextFrame);
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
}
else
{
Assert.IsNull(handler.NextFrame);
}
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame");
}
private class TestReplayFrame : ReplayFrame
{
public readonly bool IsImportant;
public readonly int FrameIndex;
public TestReplayFrame(double time, bool isImportant = false)
public TestReplayFrame(double time, bool isImportant = false, int frameIndex = 0)
: base(time)
{
IsImportant = isImportant;
FrameIndex = frameIndex;
}
}

View File

@ -1,115 +0,0 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets.Difficulty.Utils;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class LimitedCapacityStackTest
{
private const int capacity = 3;
private LimitedCapacityStack<int> stack;
[SetUp]
public void Setup()
{
stack = new LimitedCapacityStack<int>(capacity);
}
[Test]
public void TestEmptyStack()
{
Assert.AreEqual(0, stack.Count);
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
int unused = stack[0];
});
int count = 0;
foreach (var unused in stack)
count++;
Assert.AreEqual(0, count);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void TestInRangeElements(int count)
{
// e.g. 0 -> 1 -> 2
for (int i = 0; i < count; i++)
stack.Push(i);
Assert.AreEqual(count, stack.Count);
// e.g. 2 -> 1 -> 0 (reverse order)
for (int i = 0; i < stack.Count; i++)
Assert.AreEqual(count - 1 - i, stack[i]);
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
int unused = stack[i];
});
}
}
[TestCase(4)]
[TestCase(5)]
[TestCase(6)]
public void TestOverflowElements(int count)
{
// e.g. 0 -> 1 -> 2 -> 3
for (int i = 0; i < count; i++)
stack.Push(i);
Assert.AreEqual(capacity, stack.Count);
// e.g. 3 -> 2 -> 1 (reverse order)
for (int i = 0; i < stack.Count; i++)
Assert.AreEqual(count - 1 - i, stack[i]);
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
int unused = stack[i];
});
}
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(5)]
[TestCase(6)]
public void TestEnumerator(int count)
{
// e.g. 0 -> 1 -> 2 -> 3
for (int i = 0; i < count; i++)
stack.Push(i);
int enumeratorCount = 0;
int expectedValue = count - 1;
foreach (var item in stack)
{
Assert.AreEqual(expectedValue, item);
enumeratorCount++;
expectedValue--;
}
Assert.AreEqual(stack.Count, enumeratorCount);
}
}
}

View File

@ -0,0 +1,84 @@
// 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.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.NonVisual.Multiplayer
{
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
[Test]
public void TestPlayingUserTracking()
{
int id = 2000;
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
checkPlayingUserCount(0);
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
changeState(3, MultiplayerUserState.Playing);
checkPlayingUserCount(3);
changeState(3, MultiplayerUserState.Results);
checkPlayingUserCount(0);
changeState(6, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(6);
AddStep("another user left", () => Client.RemoveUser((Client.Room?.Users.Last().User).AsNonNull()));
checkPlayingUserCount(5);
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
}
[Test]
public void TestPlayingUsersUpdatedOnJoin()
{
AddStep("leave room", () => Client.LeaveRoom());
AddUntilStep("wait for room part", () => Client.Room == null);
AddStep("create room initially in gameplay", () =>
{
Room.RoomID.Value = null;
Client.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
room.Users.Add(new MultiplayerRoomUser(PLAYER_1_ID)
{
User = new User { Id = PLAYER_1_ID },
State = MultiplayerUserState.Playing
});
};
RoomManager.CreateRoom(Room);
});
AddUntilStep("wait for room join", () => Client.Room != null);
checkPlayingUserCount(1);
}
private void checkPlayingUserCount(int expectedCount)
=> AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount);
private void changeState(int userCount, MultiplayerUserState state)
=> AddStep($"{"user".ToQuantity(userCount)} in {state}", () =>
{
for (int i = 0; i < userCount; ++i)
{
var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
Client.ChangeUserState(userId, state);
}
});
}
}

View File

@ -0,0 +1,106 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.NonVisual
{
[HeadlessTest]
public class OngoingOperationTrackerTest : OsuTestScene
{
private OngoingOperationTracker tracker;
private IBindable<bool> operationInProgress;
[SetUpSteps]
public void SetUp()
{
AddStep("create tracker", () => Child = tracker = new OngoingOperationTracker());
AddStep("bind to operation status", () => operationInProgress = tracker.InProgress.GetBoundCopy());
}
[Test]
public void TestOperationTracking()
{
IDisposable firstOperation = null;
IDisposable secondOperation = null;
AddStep("begin first operation", () => firstOperation = tracker.BeginOperation());
AddAssert("first operation in progress", () => operationInProgress.Value);
AddStep("cannot start another operation",
() => Assert.Throws<InvalidOperationException>(() => tracker.BeginOperation()));
AddStep("end first operation", () => firstOperation.Dispose());
AddAssert("first operation is ended", () => !operationInProgress.Value);
AddStep("start second operation", () => secondOperation = tracker.BeginOperation());
AddAssert("second operation in progress", () => operationInProgress.Value);
AddStep("dispose first operation again", () => firstOperation.Dispose());
AddAssert("second operation still in progress", () => operationInProgress.Value);
AddStep("dispose second operation", () => secondOperation.Dispose());
AddAssert("second operation is ended", () => !operationInProgress.Value);
}
[Test]
public void TestOperationDisposalAfterTracker()
{
IDisposable operation = null;
AddStep("begin operation", () => operation = tracker.BeginOperation());
AddStep("dispose tracker", () => tracker.Expire());
AddStep("end operation", () => operation.Dispose());
AddAssert("operation is ended", () => !operationInProgress.Value);
}
[Test]
public void TestOperationDisposalAfterScreenExit()
{
TestScreenWithTracker screen = null;
OsuScreenStack stack;
IDisposable operation = null;
AddStep("create screen with tracker", () =>
{
Child = stack = new OsuScreenStack
{
RelativeSizeAxes = Axes.Both
};
stack.Push(screen = new TestScreenWithTracker());
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddStep("begin operation", () => operation = screen.OngoingOperationTracker.BeginOperation());
AddAssert("operation in progress", () => screen.OngoingOperationTracker.InProgress.Value);
AddStep("dispose after screen exit", () =>
{
screen.Exit();
operation.Dispose();
});
AddAssert("operation ended", () => !screen.OngoingOperationTracker.InProgress.Value);
}
private class TestScreenWithTracker : OsuScreen
{
public OngoingOperationTracker OngoingOperationTracker { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
InternalChild = OngoingOperationTracker = new OngoingOperationTracker();
}
}
}
}

View File

@ -0,0 +1,143 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets.Difficulty.Utils;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class ReverseQueueTest
{
private ReverseQueue<char> queue;
[SetUp]
public void Setup()
{
queue = new ReverseQueue<char>(4);
}
[Test]
public void TestEmptyQueue()
{
Assert.AreEqual(0, queue.Count);
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
char unused = queue[0];
});
int count = 0;
foreach (var unused in queue)
count++;
Assert.AreEqual(0, count);
}
[Test]
public void TestEnqueue()
{
// Assert correct values and reverse index after enqueueing
queue.Enqueue('a');
queue.Enqueue('b');
queue.Enqueue('c');
Assert.AreEqual('c', queue[0]);
Assert.AreEqual('b', queue[1]);
Assert.AreEqual('a', queue[2]);
// Assert correct values and reverse index after enqueueing beyond initial capacity of 4
queue.Enqueue('d');
queue.Enqueue('e');
queue.Enqueue('f');
Assert.AreEqual('f', queue[0]);
Assert.AreEqual('e', queue[1]);
Assert.AreEqual('d', queue[2]);
Assert.AreEqual('c', queue[3]);
Assert.AreEqual('b', queue[4]);
Assert.AreEqual('a', queue[5]);
}
[Test]
public void TestDequeue()
{
queue.Enqueue('a');
queue.Enqueue('b');
queue.Enqueue('c');
queue.Enqueue('d');
queue.Enqueue('e');
queue.Enqueue('f');
// Assert correct item return and no longer in queue after dequeueing
Assert.AreEqual('a', queue[5]);
var dequeuedItem = queue.Dequeue();
Assert.AreEqual('a', dequeuedItem);
Assert.AreEqual(5, queue.Count);
Assert.AreEqual('f', queue[0]);
Assert.AreEqual('b', queue[4]);
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
char unused = queue[5];
});
// Assert correct state after enough enqueues and dequeues to wrap around array (queue.start = 0 again)
queue.Enqueue('g');
queue.Enqueue('h');
queue.Enqueue('i');
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
Assert.AreEqual(1, queue.Count);
Assert.AreEqual('i', queue[0]);
}
[Test]
public void TestClear()
{
queue.Enqueue('a');
queue.Enqueue('b');
queue.Enqueue('c');
queue.Enqueue('d');
queue.Enqueue('e');
queue.Enqueue('f');
// Assert queue is empty after clearing
queue.Clear();
Assert.AreEqual(0, queue.Count);
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
char unused = queue[0];
});
}
[Test]
public void TestEnumerator()
{
queue.Enqueue('a');
queue.Enqueue('b');
queue.Enqueue('c');
queue.Enqueue('d');
queue.Enqueue('e');
queue.Enqueue('f');
char[] expectedValues = { 'f', 'e', 'd', 'c', 'b', 'a' };
int expectedValueIndex = 0;
// Assert items are enumerated in correct order
foreach (var item in queue)
{
Assert.AreEqual(expectedValues[expectedValueIndex], item);
expectedValueIndex++;
}
}
}
}

View File

@ -0,0 +1,47 @@
// 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 NUnit.Framework;
using osu.Game.Configuration;
using osu.Game.Input;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class SessionStaticsTest
{
private SessionStatics sessionStatics;
private IdleTracker sessionIdleTracker;
[SetUp]
public void SetUp()
{
sessionStatics = new SessionStatics();
sessionIdleTracker = new GameIdleTracker(1000);
sessionStatics.SetValue(Static.LoginOverlayDisplayed, true);
sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true);
sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true);
sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d);
sessionIdleTracker.IsIdle.BindValueChanged(e =>
{
if (e.NewValue)
sessionStatics.ResetValues();
});
}
[Test]
[Timeout(2000)]
public void TestSessionStaticsReset()
{
sessionIdleTracker.IsIdle.BindValueChanged(e =>
{
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LoginOverlayDisplayed).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime).IsDefault);
});
}
}
}

View File

@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
}
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
}

View File

@ -1,296 +0,0 @@
// 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 NUnit.Framework;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class StreamingFramedReplayInputHandlerTest
{
private Replay replay;
private TestInputHandler handler;
[SetUp]
public void SetUp()
{
handler = new TestInputHandler(replay = new Replay
{
HasReceivedAllFrames = false,
Frames = new List<ReplayFrame>
{
new TestReplayFrame(0),
new TestReplayFrame(1000),
new TestReplayFrame(2000),
new TestReplayFrame(3000, true),
new TestReplayFrame(4000, true),
new TestReplayFrame(5000, true),
new TestReplayFrame(7000, true),
new TestReplayFrame(8000),
}
});
}
[Test]
public void TestNormalPlayback()
{
Assert.IsNull(handler.CurrentFrame);
confirmCurrentFrame(null);
confirmNextFrame(0);
setTime(0, 0);
confirmCurrentFrame(0);
confirmNextFrame(1);
// if we hit the first frame perfectly, time should progress to it.
setTime(1000, 1000);
confirmCurrentFrame(1);
confirmNextFrame(2);
// in between non-important frames should progress based on input.
setTime(1200, 1200);
confirmCurrentFrame(1);
setTime(1400, 1400);
confirmCurrentFrame(1);
// progressing beyond the next frame should force time to that frame once.
setTime(2200, 2000);
confirmCurrentFrame(2);
// second attempt should progress to input time
setTime(2200, 2200);
confirmCurrentFrame(2);
// entering important section
setTime(3000, 3000);
confirmCurrentFrame(3);
// cannot progress within
setTime(3500, null);
confirmCurrentFrame(3);
setTime(4000, 4000);
confirmCurrentFrame(4);
// still cannot progress
setTime(4500, null);
confirmCurrentFrame(4);
setTime(5200, 5000);
confirmCurrentFrame(5);
// important section AllowedImportantTimeSpan allowance
setTime(5200, 5200);
confirmCurrentFrame(5);
setTime(7200, 7000);
confirmCurrentFrame(6);
setTime(7200, null);
confirmCurrentFrame(6);
// exited important section
setTime(8200, 8000);
confirmCurrentFrame(7);
confirmNextFrame(null);
setTime(8200, null);
confirmCurrentFrame(7);
confirmNextFrame(null);
setTime(8400, null);
confirmCurrentFrame(7);
confirmNextFrame(null);
}
[Test]
public void TestIntroTime()
{
setTime(-1000, -1000);
confirmCurrentFrame(null);
confirmNextFrame(0);
setTime(-500, -500);
confirmCurrentFrame(null);
confirmNextFrame(0);
setTime(0, 0);
confirmCurrentFrame(0);
confirmNextFrame(1);
}
[Test]
public void TestBasicRewind()
{
setTime(2800, 0);
setTime(2800, 1000);
setTime(2800, 2000);
setTime(2800, 2800);
confirmCurrentFrame(2);
confirmNextFrame(3);
// pivot without crossing a frame boundary
setTime(2700, 2700);
confirmCurrentFrame(2);
confirmNextFrame(1);
// cross current frame boundary; should not yet update frame
setTime(1980, 1980);
confirmCurrentFrame(2);
confirmNextFrame(1);
setTime(1200, 1200);
confirmCurrentFrame(2);
confirmNextFrame(1);
// ensure each frame plays out until start
setTime(-500, 1000);
confirmCurrentFrame(1);
confirmNextFrame(0);
setTime(-500, 0);
confirmCurrentFrame(0);
confirmNextFrame(null);
setTime(-500, -500);
confirmCurrentFrame(0);
confirmNextFrame(null);
}
[Test]
public void TestRewindInsideImportantSection()
{
fastForwardToPoint(3000);
setTime(4000, 4000);
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(3500, null);
confirmCurrentFrame(4);
confirmNextFrame(3);
setTime(3000, 3000);
confirmCurrentFrame(3);
confirmNextFrame(2);
setTime(3500, null);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(4000, 4000);
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(4500, null);
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(4000, null);
confirmCurrentFrame(4);
confirmNextFrame(5);
setTime(3500, null);
confirmCurrentFrame(4);
confirmNextFrame(3);
setTime(3000, 3000);
confirmCurrentFrame(3);
confirmNextFrame(2);
}
[Test]
public void TestRewindOutOfImportantSection()
{
fastForwardToPoint(3500);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3200, null);
// next frame doesn't change even though direction reversed, because of important section.
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(3000, null);
confirmCurrentFrame(3);
confirmNextFrame(4);
setTime(2800, 2800);
confirmCurrentFrame(3);
confirmNextFrame(2);
}
private void fastForwardToPoint(double destination)
{
for (int i = 0; i < 1000; i++)
{
if (handler.SetFrameFromTime(destination) == null)
return;
}
throw new TimeoutException("Seek was never fulfilled");
}
private void setTime(double set, double? expect)
{
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
}
private void confirmCurrentFrame(int? frame)
{
if (frame.HasValue)
{
Assert.IsNotNull(handler.CurrentFrame);
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
}
else
{
Assert.IsNull(handler.CurrentFrame);
}
}
private void confirmNextFrame(int? frame)
{
if (frame.HasValue)
{
Assert.IsNotNull(handler.NextFrame);
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
}
else
{
Assert.IsNull(handler.NextFrame);
}
}
private class TestReplayFrame : ReplayFrame
{
public readonly bool IsImportant;
public TestReplayFrame(double time, bool isImportant = false)
: base(time)
{
IsImportant = isImportant;
}
}
private class TestInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestInputHandler(Replay replay)
: base(replay)
{
FrameAccuratePlayback = true;
}
protected override double AllowedImportantTimeSpan => 1000;
protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant;
}
}
}

View File

@ -0,0 +1,111 @@
// 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Game.Utils;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class TaskChainTest
{
private TaskChain taskChain;
private int currentTask;
private CancellationTokenSource globalCancellationToken;
[SetUp]
public void Setup()
{
globalCancellationToken = new CancellationTokenSource();
taskChain = new TaskChain();
currentTask = 0;
}
[TearDown]
public void TearDown()
{
globalCancellationToken?.Cancel();
}
[Test]
public async Task TestChainedTasksRunSequentially()
{
var task1 = addTask();
var task2 = addTask();
var task3 = addTask();
task3.mutex.Set();
task2.mutex.Set();
task1.mutex.Set();
await Task.WhenAll(task1.task, task2.task, task3.task);
Assert.That(task1.task.Result, Is.EqualTo(1));
Assert.That(task2.task.Result, Is.EqualTo(2));
Assert.That(task3.task.Result, Is.EqualTo(3));
}
[Test]
public async Task TestChainedTaskWithIntermediateCancelRunsInSequence()
{
var task1 = addTask();
var task2 = addTask();
var task3 = addTask();
// Cancel task2, allow task3 to complete.
task2.cancellation.Cancel();
task2.mutex.Set();
task3.mutex.Set();
// Allow task3 to potentially complete.
Thread.Sleep(1000);
// Allow task1 to complete.
task1.mutex.Set();
// Wait on both tasks.
await Task.WhenAll(task1.task, task3.task);
Assert.That(task1.task.Result, Is.EqualTo(1));
Assert.That(task2.task.IsCompleted, Is.False);
Assert.That(task3.task.Result, Is.EqualTo(2));
}
[Test]
public async Task TestChainedTaskDoesNotCompleteBeforeChildTasks()
{
var mutex = new ManualResetEventSlim(false);
var task = taskChain.Add(async () => await Task.Run(() => mutex.Wait(globalCancellationToken.Token)));
// Allow task to potentially complete
Thread.Sleep(1000);
Assert.That(task.IsCompleted, Is.False);
// Allow the task to complete.
mutex.Set();
await task;
}
private (Task<int> task, ManualResetEventSlim mutex, CancellationTokenSource cancellation) addTask()
{
var mutex = new ManualResetEventSlim(false);
var completionSource = new TaskCompletionSource<int>();
var cancellationSource = new CancellationTokenSource();
var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationSource.Token, globalCancellationToken.Token);
taskChain.Add(() =>
{
mutex.Wait(globalCancellationToken.Token);
completionSource.SetResult(Interlocked.Increment(ref currentTask));
}, token.Token);
return (completionSource.Task, mutex, cancellationSource);
}
}
}

View File

@ -0,0 +1,195 @@
// 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.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Tests.Online
{
[TestFixture]
public class TestAPIModJsonSerialization
{
[Test]
public void TestAcronymIsPreserved()
{
var apiMod = new APIMod(new TestMod());
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
Assert.That(deserialized?.Acronym, Is.EqualTo(apiMod.Acronym));
}
[Test]
public void TestRawSettingIsPreserved()
{
var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
Assert.That(deserialized?.Settings, Contains.Key("test_setting").With.ContainValue(2.0));
}
[Test]
public void TestConvertedModHasCorrectSetting()
{
var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestMod)deserialized?.ToMod(new TestRuleset());
Assert.That(converted?.TestSetting.Value, Is.EqualTo(2));
}
[Test]
public void TestDeserialiseTimeRampMod()
{
// Create the mod with values different from default.
var apiMod = new APIMod(new TestModTimeRamp
{
AdjustPitch = { Value = false },
InitialRate = { Value = 1.25 },
FinalRate = { Value = 0.25 }
});
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestModTimeRamp)deserialised?.ToMod(new TestRuleset());
Assert.That(converted?.AdjustPitch.Value, Is.EqualTo(false));
Assert.That(converted?.InitialRate.Value, Is.EqualTo(1.25));
Assert.That(converted?.FinalRate.Value, Is.EqualTo(0.25));
}
[Test]
public void TestDeserialiseDifficultyAdjustModWithExtendedLimits()
{
var apiMod = new APIMod(new TestModDifficultyAdjust
{
OverallDifficulty = { Value = 11 },
ExtendedLimits = { Value = true }
});
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestModDifficultyAdjust)deserialised?.ToMod(new TestRuleset());
Assert.That(converted?.ExtendedLimits.Value, Is.True);
Assert.That(converted?.OverallDifficulty.Value, Is.EqualTo(11));
}
[Test]
public void TestDeserialiseScoreInfoWithEmptyMods()
{
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
if (deserialised != null)
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
Assert.That(deserialised?.Mods.Length, Is.Zero);
}
[Test]
public void TestDeserialiseScoreInfoWithCustomModSetting()
{
var score = new ScoreInfo
{
Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
};
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
if (deserialised != null)
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2));
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
{
new TestMod(),
new TestModTimeRamp(),
new TestModDifficultyAdjust()
};
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException();
public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty;
}
private class TestMod : Mod
{
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
public BindableNumber<double> TestSetting { get; } = new BindableDouble
{
MinValue = 0,
MaxValue = 10,
Default = 5,
Precision = 0.01,
};
}
private class TestModTimeRamp : ModTimeRamp
{
public override string Name => "Test Mod";
public override string Acronym => "TMTR";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{
MinValue = 1,
MaxValue = 2,
Default = 1.5,
Value = 1.5,
Precision = 0.01,
};
[SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{
MinValue = 0,
MaxValue = 1,
Default = 0.5,
Value = 0.5,
Precision = 0.01,
};
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public override BindableBool AdjustPitch { get; } = new BindableBool
{
Default = true,
Value = true
};
}
private class TestModDifficultyAdjust : ModDifficultyAdjust
{
}
}
}

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using MessagePack;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@ -16,14 +16,14 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Tests.Online
{
[TestFixture]
public class TestAPIModSerialization
public class TestAPIModMessagePackSerialization
{
[Test]
public void TestAcronymIsPreserved()
{
var apiMod = new APIMod(new TestMod());
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var deserialized = MessagePackSerializer.Deserialize<APIMod>(MessagePackSerializer.Serialize(apiMod));
Assert.That(deserialized.Acronym, Is.EqualTo(apiMod.Acronym));
}
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Online
{
var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var deserialized = MessagePackSerializer.Deserialize<APIMod>(MessagePackSerializer.Serialize(apiMod));
Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(2.0));
}
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Online
{
var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
var deserialized = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var deserialized = MessagePackSerializer.Deserialize<APIMod>(MessagePackSerializer.Serialize(apiMod));
var converted = (TestMod)deserialized.ToMod(new TestRuleset());
Assert.That(converted.TestSetting.Value, Is.EqualTo(2));
@ -60,7 +60,7 @@ namespace osu.Game.Tests.Online
FinalRate = { Value = 0.25 }
});
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var deserialised = MessagePackSerializer.Deserialize<APIMod>(MessagePackSerializer.Serialize(apiMod));
var converted = (TestModTimeRamp)deserialised.ToMod(new TestRuleset());
Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false));
@ -68,6 +68,16 @@ namespace osu.Game.Tests.Online
Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25));
}
[Test]
public void TestDeserialiseEnumMod()
{
var apiMod = new APIMod(new TestModEnum { TestSetting = { Value = TestEnum.Value2 } });
var deserialized = MessagePackSerializer.Deserialize<APIMod>(MessagePackSerializer.Serialize(apiMod));
Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(1));
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
@ -90,6 +100,7 @@ namespace osu.Game.Tests.Online
{
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
@ -106,6 +117,7 @@ namespace osu.Game.Tests.Online
{
public override string Name => "Test Mod";
public override string Acronym => "TMTR";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
@ -135,5 +147,23 @@ namespace osu.Game.Tests.Online
Value = true
};
}
private class TestModEnum : Mod
{
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
public Bindable<TestEnum> TestSetting { get; } = new Bindable<TestEnum>();
}
private enum TestEnum
{
Value1 = 0,
Value2 = 1,
Value3 = 2
}
}
}

View File

@ -23,8 +23,10 @@ namespace osu.Game.Tests.Online
{
case CommentVoteRequest cRequest:
cRequest.TriggerSuccess(new CommentBundle());
break;
return true;
}
return false;
});
CommentVoteRequest request = null;
@ -108,8 +110,10 @@ namespace osu.Game.Tests.Online
{
case LeaveChannelRequest cRequest:
cRequest.TriggerSuccess();
break;
return true;
}
return false;
});
}
}

View File

@ -0,0 +1,188 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Online
{
[HeadlessTest]
public class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene
{
private RulesetStore rulesets;
private TestBeatmapManager beatmaps;
private string testBeatmapFile;
private BeatmapInfo testBeatmapInfo;
private BeatmapSetInfo testBeatmapSet;
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
private OnlinePlayBeatmapAvailabilityTracker availabilityTracker;
[BackgroundDependencyLoader]
private void load(AudioManager audio, GameHost host)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, host, Beatmap.Default));
}
[SetUp]
public void SetUp() => Schedule(() =>
{
beatmaps.AllowImport = new TaskCompletionSource<bool>();
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID);
if (existing != null)
beatmaps.Delete(existing);
selectedItem.Value = new PlaylistItem
{
Beatmap = { Value = testBeatmapInfo },
Ruleset = { Value = testBeatmapInfo.Ruleset },
};
Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem, }
};
});
[Test]
public void TestBeatmapDownloadingFlow()
{
AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet));
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("start downloading", () => beatmaps.Download(testBeatmapSet));
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f));
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile));
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true);
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
}
[Test]
public void TestTrackerRespectsSoftDeleting()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
}
[Test]
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import altered beatmap", () =>
{
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
});
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem }
});
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
}
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)
{
AddAssert(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke()));
}
private static BeatmapInfo getTestBeatmapInfo(string archiveFile)
{
BeatmapInfo info;
using (var archive = new ZipArchiveReader(File.OpenRead(archiveFile)))
using (var stream = archive.GetStream("Soleily - Renatus (Gamu) [Insane].osu"))
using (var reader = new LineBufferedReader(stream))
{
var decoder = Decoder.GetDecoder<Beatmap>(reader);
var beatmap = decoder.Decode(reader);
info = beatmap.BeatmapInfo;
info.BeatmapSet.Beatmaps = new List<BeatmapInfo> { info };
info.BeatmapSet.Metadata = info.Metadata;
info.MD5Hash = stream.ComputeMD5Hash();
info.Hash = stream.ComputeSHA2Hash();
}
return info;
}
private class TestBeatmapManager : BeatmapManager
{
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
public Task<BeatmapSetInfo> CurrentImportTask { get; private set; }
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
=> new TestDownloadRequest(set);
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
: base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups)
{
}
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
await AllowImport.Task;
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken));
}
}
private class TestDownloadRequest : ArchiveDownloadRequest<BeatmapSetInfo>
{
public new void SetProgress(float progress) => base.SetProgress(progress);
public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename);
public TestDownloadRequest(BeatmapSetInfo model)
: base(model)
{
}
protected override string Target => null;
}
}
}

View File

@ -0,0 +1,35 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.OnlinePlay
{
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
[Test]
public void TestUserAddedOnJoin()
{
var user = new User { Id = 33 };
AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
}
[Test]
public void TestUserRemovedOnLeave()
{
var user = new User { Id = 44 };
AddStep("add user", () => Client.AddUser(user));
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
}
}
}

View File

@ -0,0 +1,223 @@
// 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 NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.OnlinePlay
{
[HeadlessTest]
public class TestSceneCatchUpSyncManager : OsuTestScene
{
private TestManualClock master;
private CatchUpSyncManager syncManager;
private TestSpectatorPlayerClock player1;
private TestSpectatorPlayerClock player2;
[SetUp]
public void Setup()
{
syncManager = new CatchUpSyncManager(master = new TestManualClock());
syncManager.AddPlayerClock(player1 = new TestSpectatorPlayerClock(1));
syncManager.AddPlayerClock(player2 = new TestSpectatorPlayerClock(2));
Schedule(() => Child = syncManager);
}
[Test]
public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames()
{
setWaiting(() => player1, false);
assertMasterState(false);
assertPlayerClockState(() => player1, false);
assertPlayerClockState(() => player2, false);
setWaiting(() => player2, false);
assertMasterState(true);
assertPlayerClockState(() => player1, true);
assertPlayerClockState(() => player2, true);
}
[Test]
public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime()
{
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
assertMasterState(false);
}
[Test]
public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
{
setWaiting(() => player1, false);
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
assertMasterState(true);
}
[Test]
public void TestPlayerClockDoesNotCatchUpWhenSlightlyOutOfSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1);
assertCatchingUp(() => player1, false);
}
[Test]
public void TestPlayerClockStartsCatchingUpWhenTooFarBehind()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
assertCatchingUp(() => player1, true);
assertCatchingUp(() => player2, true);
}
[Test]
public void TestPlayerClockKeepsCatchingUpWhenSlightlyOutOfSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET + 1);
assertCatchingUp(() => player1, true);
}
[Test]
public void TestPlayerClockStopsCatchingUpWhenInSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2);
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => player1, false);
assertCatchingUp(() => player2, true);
}
[Test]
public void TestPlayerClockDoesNotStopWhenSlightlyAhead()
{
setAllWaiting(false);
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, true);
}
[Test]
public void TestPlayerClockStopsWhenTooFarAheadAndStartsWhenBackInSync()
{
setAllWaiting(false);
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET - 1);
// This is a silent catchup, where IsCatchingUp = false but IsRunning = false also.
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, false);
setMasterTime(1);
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, true);
}
[Test]
public void TestInSyncPlayerClockDoesNotStartIfWaitingOnFrames()
{
setAllWaiting(false);
assertPlayerClockState(() => player1, true);
setWaiting(() => player1, true);
assertPlayerClockState(() => player1, false);
}
private void setWaiting(Func<TestSpectatorPlayerClock> playerClock, bool waiting)
=> AddStep($"set player clock {playerClock().Id} waiting = {waiting}", () => playerClock().WaitingOnFrames.Value = waiting);
private void setAllWaiting(bool waiting) => AddStep($"set all player clocks waiting = {waiting}", () =>
{
player1.WaitingOnFrames.Value = waiting;
player2.WaitingOnFrames.Value = waiting;
});
private void setMasterTime(double time)
=> AddStep($"set master = {time}", () => master.Seek(time));
/// <summary>
/// clock.Time = master.Time - offsetFromMaster
/// </summary>
private void setPlayerClockTime(Func<TestSpectatorPlayerClock> playerClock, double offsetFromMaster)
=> AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster));
private void assertMasterState(bool running)
=> AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
private void assertCatchingUp(Func<TestSpectatorPlayerClock> playerClock, bool catchingUp) =>
AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp);
private void assertPlayerClockState(Func<TestSpectatorPlayerClock> playerClock, bool running)
=> AddAssert($"player clock {playerClock().Id} {(running ? "is" : "is not")} running", () => playerClock().IsRunning == running);
private class TestSpectatorPlayerClock : TestManualClock, ISpectatorPlayerClock
{
public Bindable<bool> WaitingOnFrames { get; } = new Bindable<bool>(true);
public bool IsCatchingUp { get; set; }
public IFrameBasedClock Source
{
set => throw new NotImplementedException();
}
public readonly int Id;
public TestSpectatorPlayerClock(int id)
{
Id = id;
WaitingOnFrames.BindValueChanged(waiting =>
{
if (waiting.NewValue)
Stop();
else
Start();
});
}
public void ProcessFrame()
{
}
public double ElapsedFrameTime => 0;
public double FramesPerSecond => 0;
public FrameTimeInfo TimeInfo => default;
}
private class TestManualClock : ManualClock, IAdjustableClock
{
public void Start() => IsRunning = true;
public void Stop() => IsRunning = false;
public bool Seek(double position)
{
CurrentTime = position;
return true;
}
public void Reset()
{
}
public void ResetSpeedAdjustments()
{
}
}
}
}

View File

@ -15,6 +15,28 @@ namespace osu.Game.Tests.Resources
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
/// <summary>
/// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track.
/// </summary>
/// <remarks>
/// This is intended for use in tests which need to run to completion as soon as possible and don't need to test a full length beatmap.</remarks>
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetQuickTestBeatmapForImport()
{
var tempPath = Path.GetTempFileName() + ".osz";
using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
using (var newFile = File.Create(tempPath))
stream.CopyTo(newFile);
Assert.IsTrue(File.Exists(tempPath));
return tempPath;
}
/// <summary>
/// Retrieve a path to a copy of a full-fledged beatmap archive.
/// </summary>
/// <param name="virtualTrack">Whether the audio track should be virtual.</param>
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
var tempPath = Path.GetTempFileName() + ".osz";

View File

@ -0,0 +1,9 @@
osu file format v14
[Events]
Animation,Foreground,Centre,"forever-string.png",330,240,10,108,LoopForever
Animation,Foreground,Centre,"once-string.png",330,240,10,108,LoopOnce
Animation,Foreground,Centre,"forever-number.png",330,240,10,108,0
Animation,Foreground,Centre,"once-number.png",330,240,10,108,1
Animation,Foreground,Centre,"undefined-number.png",330,240,10,108,16
Animation,Foreground,Centre,"omitted.png",330,240,10,108

View File

@ -9,3 +9,16 @@ osu file format v128
// Implicit multi-segment
32,192,3000,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
// Last control point duplicated
0,0,4000,2,0,B|1:1|2:2|3:3|3:3,2,200
// Last control point in segment duplicated
0,0,5000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200
// Implicit perfect-curve multi-segment (Should convert to bezier to match stable)
0,0,6000,2,0,P|75:145|170:75|170:75|300:145|410:20,1,475,0:0:0:0:
// Explicit perfect-curve multi-segment (Should not convert to bezier)
0,0,7000,2,0,P|75:145|P|170:75|300:145|410:20,1,650,0:0:0:0:

View File

@ -0,0 +1,6 @@
[Events]
//Storyboard Layer 0 (Background)
Sprite,Background,TopCentre,"img.jpg",320,240
F,0,1500,1600,0,1
Sprite,Background,TopCentre,"img.jpg",320,240
F,0,1000,1100,0,1

View File

@ -0,0 +1,2 @@
[General]
Version: 2

View File

@ -0,0 +1,113 @@
// 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 NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Tests.Rulesets.Mods
{
[TestFixture]
public class ModTimeRampTest
{
private const double start_time = 1000;
private const double duration = 9000;
private TrackVirtual track;
[SetUp]
public void SetUp()
{
track = new TrackVirtual(20_000);
}
[TestCase(0, 1)]
[TestCase(start_time, 1)]
[TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 1.25)]
[TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 1.5)]
[TestCase(start_time + duration, 1.5)]
[TestCase(15000, 1.5)]
public void TestModWindUp(double time, double expectedRate)
{
var beatmap = createSingleSpinnerBeatmap();
var mod = new ModWindUp();
mod.ApplyToBeatmap(beatmap);
mod.ApplyToTrack(track);
seekTrackAndUpdateMod(mod, time);
Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate));
}
[TestCase(0, 1)]
[TestCase(start_time, 1)]
[TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 0.75)]
[TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 0.5)]
[TestCase(start_time + duration, 0.5)]
[TestCase(15000, 0.5)]
public void TestModWindDown(double time, double expectedRate)
{
var beatmap = createSingleSpinnerBeatmap();
var mod = new ModWindDown
{
FinalRate = { Value = 0.5 }
};
mod.ApplyToBeatmap(beatmap);
mod.ApplyToTrack(track);
seekTrackAndUpdateMod(mod, time);
Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate));
}
[TestCase(0, 1)]
[TestCase(start_time, 1)]
[TestCase(2 * start_time, 1.5)]
public void TestZeroDurationMap(double time, double expectedRate)
{
var beatmap = createSingleObjectBeatmap();
var mod = new ModWindUp();
mod.ApplyToBeatmap(beatmap);
mod.ApplyToTrack(track);
seekTrackAndUpdateMod(mod, time);
Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate));
}
private void seekTrackAndUpdateMod(ModTimeRamp mod, double time)
{
track.Seek(time);
// update the mod via a fake playfield to re-calculate the current rate.
mod.Update(null);
}
private static Beatmap createSingleSpinnerBeatmap()
{
return new Beatmap
{
HitObjects =
{
new Spinner
{
StartTime = start_time,
Duration = duration
}
}
};
}
private static Beatmap createSingleObjectBeatmap()
{
return new Beatmap
{
HitObjects =
{
new HitCircle { StartTime = start_time }
}
};
}
}
}

View File

@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
@ -51,7 +50,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
};
scoreProcessor.ApplyResult(judgementResult);
Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value));
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d));
}
/// <summary>
@ -77,27 +76,28 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] // (3 * 0) / (4 * 300) * 300_000 + (0 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] // (3 * 50) / (4 * 300) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] // (3 * 100) / (4 * 300) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 492_857)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] // (3 * 300) / (4 * 300) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] // (3 * 350) / (4 * 350) * 300_000 + (2 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] // (3 * 0) / (4 * 10) * 300_000 + 700_000 (max combo 0)
[TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0)
[TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points)
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points)
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points)
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points)
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25)
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 594)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25)
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25)
[TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25)
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25)
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points)
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points)
// TODO: The following two cases don't match expectations currently (a single hit is registered in acc portion when it shouldn't be). See https://github.com/ppy/osu/issues/12604.
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points)
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points)
public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore)
{
var minResult = new TestJudgement(hitResult).MinResult;
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
scoreProcessor.ApplyResult(judgementResult);
}
Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5));
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d));
}
/// <remarks>
@ -158,7 +158,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
};
scoreProcessor.ApplyResult(lastJudgementResult);
Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5));
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d));
}
[Test]
@ -169,7 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
scoreProcessor.Mode.Value = scoringMode;
scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo()));
Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value));
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
}
[TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)]
@ -287,6 +287,23 @@ namespace osu.Game.Tests.Rulesets.Scoring
Assert.AreEqual(expectedReturnValue, hitResult.IsScorable());
}
[TestCase(HitResult.Perfect, 1_000_000)]
[TestCase(HitResult.SmallTickHit, 1_000_000)]
[TestCase(HitResult.LargeTickHit, 1_000_000)]
[TestCase(HitResult.SmallBonus, 1_000_000 + Judgement.SMALL_BONUS_SCORE)]
[TestCase(HitResult.LargeBonus, 1_000_000 + Judgement.LARGE_BONUS_SCORE)]
public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore)
{
var statistic = new Dictionary<HitResult, int> { { result, 1 } };
scoreProcessor.ApplyBeatmap(new Beatmap
{
HitObjects = { new TestHitObject(result) }
});
Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d));
}
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }

View File

@ -9,7 +9,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -106,9 +105,9 @@ namespace osu.Game.Tests.Rulesets
IsDisposed = true;
}
public SampleChannel Get(string name) => null;
public Sample Get(string name) => null;
public Task<SampleChannel> GetAsync(string name) => null;
public Task<Sample> GetAsync(string name) => null;
public Stream GetStream(string name) => null;
@ -119,9 +118,13 @@ namespace osu.Game.Tests.Rulesets
public BindableNumber<double> Frequency => throw new NotImplementedException();
public BindableNumber<double> Tempo => throw new NotImplementedException();
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException();

View File

@ -113,6 +113,31 @@ namespace osu.Game.Tests.Skins.IO
}
}
[Test]
public async Task TestImportUpperCasedOskArchive()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
{
try
{
var osu = LoadOsuIntoHost(host);
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK"));
Assert.That(imported.Name, Is.EqualTo("name 1"));
Assert.That(imported.Creator, Is.EqualTo("author 1"));
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK"));
Assert.That(imported2.Hash, Is.EqualTo(imported.Hash));
}
finally
{
host.Exit();
}
}
}
private MemoryStream createOsk(string name, string author)
{
var zipStream = new MemoryStream();

View File

@ -91,6 +91,15 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
[Test]
public void TestStripWhitespace()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-with-space.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
[Test]
public void TestDecodeLatestVersion()
{

View File

@ -219,7 +219,7 @@ namespace osu.Game.Tests.Skins
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
}

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Background
public void SetUp() => Schedule(() =>
{
// reset API response in statics to avoid test crosstalk.
statics.Set<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
statics.SetValue<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
textureStore.PerformedLookups.Clear();
dummyAPI.SetState(APIState.Online);
@ -135,18 +135,20 @@ namespace osu.Game.Tests.Visual.Background
dummyAPI.HandleRequest = request =>
{
if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
return;
return false;
backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
{
Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
EndDate = endDate
});
return true;
};
});
private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
=> AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode));
=> AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));
private void createLoader()
=> AddStep("create loader", () =>

View File

@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
@ -65,6 +65,21 @@ namespace osu.Game.Tests.Visual.Background
stack.Push(songSelect = new DummySongSelect());
});
/// <summary>
/// User settings should always be ignored on song select screen.
/// </summary>
[Test]
public void TestUserSettingsIgnoredOnSongSelect()
{
setupUserSettings();
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
}
/// <summary>
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
/// </summary>
@ -82,7 +97,7 @@ namespace osu.Game.Tests.Visual.Background
});
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
/// <summary>
@ -106,6 +121,7 @@ namespace osu.Game.Tests.Visual.Background
public void TestStoryboardBackgroundVisibility()
{
performFullSetup();
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
@ -141,9 +157,9 @@ namespace osu.Game.Tests.Visual.Background
{
performFullSetup();
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
AddStep("Disable user dim", () => songSelect.IgnoreUserSettings.Value = true);
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
AddStep("Enable user dim", () => songSelect.IgnoreUserSettings.Value = false);
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
@ -160,13 +176,36 @@ namespace osu.Game.Tests.Visual.Background
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Enable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = false);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
AddStep("Disable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true);
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
}
[Test]
public void TestStoryboardIgnoreUserSettings()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable replacing background", () => player.ReplacesBackground.Value = true);
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
AddStep("Ignore user settings", () =>
{
player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
});
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
AddUntilStep("Background is invisible", () => songSelect.IsBackgroundInvisible());
AddStep("Disable background replacement", () => player.ReplacesBackground.Value = false);
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
}
/// <summary>
/// Check if the visual settings container retains dim and blur when pausing
/// </summary>
@ -198,19 +237,9 @@ namespace osu.Game.Tests.Visual.Background
})));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
AddUntilStep("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
}
/// <summary>
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
/// </summary>
[Test]
public void TestTransitionOut()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
AddUntilStep("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
}
/// <summary>
@ -224,7 +253,7 @@ namespace osu.Game.Tests.Visual.Background
AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
@ -274,14 +303,16 @@ namespace osu.Game.Tests.Visual.Background
private class DummySongSelect : PlaySongSelect
{
private FadeAccessibleBackground background;
protected override BackgroundScreen CreateBackground()
{
FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
background = new FadeAccessibleBackground(Beatmap.Value);
IgnoreUserSettings.BindTo(background.IgnoreUserSettings);
return background;
}
public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
public readonly Bindable<bool> IgnoreUserSettings = new Bindable<bool>();
public readonly Bindable<double> DimLevel = new BindableDouble();
public readonly Bindable<double> BlurLevel = new BindableDouble();
@ -294,25 +325,27 @@ namespace osu.Game.Tests.Visual.Background
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim);
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
public bool IsBackgroundInvisible() => background.CurrentAlpha == 0;
public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
/// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced
/// </summary>
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
public bool IsBackgroundCurrent() => background?.IsCurrentScreen() == true;
}
private class FadeAccessibleResults : ResultsScreen
@ -324,12 +357,20 @@ namespace osu.Game.Tests.Visual.Background
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
}
private class LoadBlockingTestPlayer : TestPlayer
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
protected override BackgroundScreen CreateBackground() =>
new FadeAccessibleBackground(Beatmap.Value);
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
ApplyToBackground(b => ReplacesBackground.BindTo(b.StoryboardReplacesBackground));
}
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
@ -354,15 +395,16 @@ namespace osu.Game.Tests.Visual.Background
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
DrawableRuleset.IsPaused.BindTo(IsPaused);
}
}
private class TestPlayerLoader : PlayerLoader
{
private FadeAccessibleBackground background;
public VisualSettings VisualSettingsPos => VisualSettings;
public BackgroundScreen ScreenPos => Background;
public BackgroundScreen ScreenPos => background;
public TestPlayerLoader(Player player)
: base(() => player)
@ -371,9 +413,9 @@ namespace osu.Game.Tests.Visual.Background
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
protected override BackgroundScreen CreateBackground() => background = new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap

View File

@ -38,13 +38,13 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
base.Content.AddRange(new Drawable[]
{
manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay()
dialogOverlay = new DialogOverlay(),
});
Dependencies.Cache(manager);
@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections
assertCollectionName(0, "2");
}
[Test]
public void TestCollectionNameCollisions()
{
AddStep("add dropdown", () =>
{
Add(new CollectionFilterDropdown
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
Width = 0.4f,
}
);
});
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
}));
}
[Test]
public void TestRemoveCollectionViaButton()
{

View File

@ -81,6 +81,13 @@ namespace osu.Game.Tests.Visual.Components
[Test]
public void TestMovement()
{
checkIdleStatus(1, false);
checkIdleStatus(2, false);
checkIdleStatus(3, false);
checkIdleStatus(4, false);
waitForAllIdle();
AddStep("move to top right", () => InputManager.MoveMouseTo(box2));
checkIdleStatus(1, true);
@ -102,6 +109,8 @@ namespace osu.Game.Tests.Visual.Components
[Test]
public void TestTimings()
{
waitForAllIdle();
AddStep("move to centre", () => InputManager.MoveMouseTo(Content));
checkIdleStatus(1, false);
@ -140,7 +149,7 @@ namespace osu.Game.Tests.Visual.Components
private void waitForAllIdle()
{
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
AddUntilStep("wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}
private class IdleTrackingBox : CompositeDrawable
@ -149,7 +158,7 @@ namespace osu.Game.Tests.Visual.Components
public bool IsIdle => idleTracker.IsIdle.Value;
public IdleTrackingBox(double timeToIdle)
public IdleTrackingBox(int timeToIdle)
{
Box box;
@ -158,7 +167,7 @@ namespace osu.Game.Tests.Visual.Components
InternalChildren = new Drawable[]
{
idleTracker = new IdleTracker(timeToIdle),
idleTracker = new GameIdleTracker(timeToIdle),
box = new Box
{
Colour = Color4.White,

View File

@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Components
{
createPoller(true);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
checkCount(1);
checkCount(2);
checkCount(3);
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
checkCount(4);
checkCount(4);
checkCount(4);
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Components
checkCount(5);
checkCount(5);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
checkCount(6);
checkCount(7);
}
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Components
{
createPoller(false);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
checkCount(0);
skip();
checkCount(0);
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Components
public class TestSlowPoller : TestPoller
{
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls.Value / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
}
}
}

View File

@ -8,7 +8,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual.Components
[Test]
public void TestNonPresentTrack()
{
TestPreviewTrack track = null;
TestPreviewTrackManager.TestPreviewTrack track = null;
AddStep("get non-present track", () =>
{
@ -182,9 +181,9 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrack getOwnedTrack()
private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{
var track = getTrack();

View File

@ -0,0 +1,70 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneBlueprintSelection : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
private EditorBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
[Test]
public void TestSelectedObjectHasPriorityWhenOverlapping()
{
var firstSlider = new Slider
{
Path = new SliderPath(new[]
{
new PathControlPoint(new Vector2()),
new PathControlPoint(new Vector2(150, -50)),
new PathControlPoint(new Vector2(300, 0))
}),
Position = new Vector2(0, 100)
};
var secondSlider = new Slider
{
Path = new SliderPath(new[]
{
new PathControlPoint(new Vector2()),
new PathControlPoint(new Vector2(-50, 50)),
new PathControlPoint(new Vector2(-100, 100))
}),
Position = new Vector2(200, 0)
};
AddStep("add overlapping sliders", () =>
{
EditorBeatmap.Add(firstSlider);
EditorBeatmap.Add(secondSlider);
});
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider));
AddStep("move mouse to common point", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider);
}
}
}

View File

@ -1,45 +1,50 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneComposeSelectBox : OsuTestScene
public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
{
private Container selectionArea;
private SelectionBox selectionBox;
public TestSceneComposeSelectBox()
[SetUp]
public void SetUp() => Schedule(() =>
{
SelectionBox selectionBox = null;
AddStep("create box", () =>
Child = selectionArea = new Container
Child = selectionArea = new Container
{
Size = new Vector2(400),
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{
Size = new Vector2(400),
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
selectionBox = new SelectionBox
{
selectionBox = new SelectionBox
{
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
RelativeSizeAxes = Axes.Both,
OnRotation = handleRotation,
OnScale = handleScale
}
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
OnRotation = handleRotation,
OnScale = handleScale
}
});
}
};
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
}
InputManager.MoveMouseTo(selectionBox);
InputManager.ReleaseButton(MouseButton.Left);
});
private bool handleScale(Vector2 amount, Anchor reference)
{
@ -68,5 +73,115 @@ namespace osu.Game.Tests.Visual.Editing
selectionArea.Rotation += angle;
return true;
}
[Test]
public void TestRotationHandleShownOnHover()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestRotationHandleShownOnHoveringClosestScaleHandler()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoverRotationHandleFromScaleHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered);
AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle));
AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle hovered", () => rotationHandle.IsHovered);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoldingScaleHandleHidesCorrespondingRotationHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
int i;
ScheduledDelegate mouseMove = null;
AddStep("start dragging", () =>
{
i = 0;
mouseMove = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i));
}, 100, true);
});
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("end dragging", () => mouseMove.Cancel());
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
}
/// <summary>
/// Tests that hovering over two handles instantaneously from one to another does not crash or cause issues to the visibility state.
/// </summary>
[Test]
public void TestHoverOverTwoHandlesInstantaneously()
{
AddStep("hover over top-left scale handle", () =>
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopLeft)));
AddStep("hover over top-right scale handle", () =>
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopRight)));
AddUntilStep("top-left rotation handle hidden", () =>
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopLeft).Alpha == 0);
AddUntilStep("top-right rotation handle shown", () =>
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopRight).Alpha == 1);
}
}
}

View File

@ -3,12 +3,16 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Tests.Beatmaps;
using osuTK;
@ -110,8 +114,9 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000);
}
[Test]
public void TestCopyPaste()
[TestCase(false)]
[TestCase(true)]
public void TestCopyPaste(bool deselectAfterCopy)
{
var addedObject = new HitCircle { StartTime = 1000 };
@ -123,11 +128,22 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move forward in time", () => EditorClock.Seek(2000));
if (deselectAfterCopy)
{
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<SelectionBox>().First().Alpha == 0);
AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<SelectionBox>().First().Alpha == 0);
}
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2);
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000);
AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
}
[Test]

View File

@ -0,0 +1,87 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorClock : EditorClockTestScene
{
public TestSceneEditorClock()
{
Add(new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new TimeInfoContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 100)
},
new PlaybackControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 100)
}
}
});
}
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
// ensure that music controller does not change this beatmap due to it
// completing naturally as part of the test.
Beatmap.Disabled = true;
}
[Test]
public void TestStopAtTrackEnd()
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("start clock", Clock.Start);
AddAssert("clock running", () => Clock.IsRunning);
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
AddUntilStep("clock stops", () => !Clock.IsRunning);
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("start clock again", Clock.Start);
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
[Test]
public void TestWrapWhenStoppedAtTrackEnd()
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("stop clock", Clock.Stop);
AddAssert("clock stopped", () => !Clock.IsRunning);
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("start clock again", Clock.Start);
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
base.Dispose(isDisposing);
}
}
}

View File

@ -1,88 +0,0 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorQuickDelete : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
private BlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<BlueprintContainer>().First();
[Test]
public void TestQuickDeleteRemovesObject()
{
var addedObject = new HitCircle { StartTime = 1000 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("move mouse to object", () =>
{
var pos = blueprintContainer.ChildrenOfType<HitCirclePiece>().First().ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestQuickDeleteRemovesSliderControlPoint()
{
Slider slider = new Slider { StartTime = 1000 };
PathControlPoint[] points =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(50, 0)),
new PathControlPoint(new Vector2(100, 0))
};
AddStep("add slider", () =>
{
slider.Path = new SliderPath(points);
EditorBeatmap.Add(slider);
});
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to controlpoint", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2);
// second click should nuke the object completely.
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
}
}

View File

@ -3,11 +3,11 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Editing
{
@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual.Editing
public void TestSlidingSampleStopsOnSeek()
{
DrawableSlider slider = null;
DrawableSample[] loopingSamples = null;
DrawableSample[] onceOffSamples = null;
PoolableSkinnableSample[] loopingSamples = null;
PoolableSkinnableSample[] onceOffSamples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
onceOffSamples = slider.ChildrenOfType<DrawableSample>().Where(s => !s.Looping).ToArray();
loopingSamples = slider.ChildrenOfType<DrawableSample>().Where(s => s.Looping).ToArray();
onceOffSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => !s.Looping).ToArray();
loopingSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => s.Looping).ToArray();
});
AddStep("start playback", () => EditorClock.Start());

View File

@ -0,0 +1,79 @@
// 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 NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorSeeking : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = base.CreateBeatmap(ruleset);
beatmap.BeatmapInfo.BeatDivisor = 1;
beatmap.ControlPointInfo = new ControlPointInfo();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
beatmap.ControlPointInfo.Add(2000, new TimingControlPoint { BeatLength = 500 });
return beatmap;
}
[Test]
public void TestSnappedSeeking()
{
AddStep("seek to 0", () => EditorClock.Seek(0));
AddAssert("time is 0", () => EditorClock.CurrentTime == 0);
pressAndCheckTime(Key.Right, 1000);
pressAndCheckTime(Key.Right, 2000);
pressAndCheckTime(Key.Right, 2500);
pressAndCheckTime(Key.Right, 3000);
pressAndCheckTime(Key.Left, 2500);
pressAndCheckTime(Key.Left, 2000);
pressAndCheckTime(Key.Left, 1000);
}
[Test]
public void TestSnappedSeekingAfterControlPointChange()
{
AddStep("seek to 0", () => EditorClock.Seek(0));
AddAssert("time is 0", () => EditorClock.CurrentTime == 0);
pressAndCheckTime(Key.Right, 1000);
pressAndCheckTime(Key.Right, 2000);
pressAndCheckTime(Key.Right, 2500);
pressAndCheckTime(Key.Right, 3000);
AddStep("remove 2nd timing point", () =>
{
EditorBeatmap.BeginChange();
var group = EditorBeatmap.ControlPointInfo.GroupAt(2000);
EditorBeatmap.ControlPointInfo.RemoveGroup(group);
EditorBeatmap.EndChange();
});
pressAndCheckTime(Key.Left, 2000);
pressAndCheckTime(Key.Left, 1000);
pressAndCheckTime(Key.Right, 2000);
pressAndCheckTime(Key.Right, 3000);
}
private void pressAndCheckTime(Key key, double expectedTime)
{
AddStep($"press {key}", () => InputManager.Key(key));
AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1));
}
}
}

View File

@ -0,0 +1,268 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorSelection : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
private EditorBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
private void moveMouseToObject(Func<HitObject> targetFunc)
{
AddStep("move mouse to object", () =>
{
var pos = blueprintContainer.SelectionBlueprints
.First(s => s.Item == targetFunc())
.ChildrenOfType<HitCirclePiece>()
.First().ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
}
[Test]
public void TestNudgeSelection()
{
HitCircle[] addedObjects = null;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
}));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
AddStep("nudge forwards", () => InputManager.Key(Key.K));
AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100);
AddStep("nudge backwards", () => InputManager.Key(Key.J));
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
}
[Test]
public void TestBasicSelect()
{
var addedObject = new HitCircle { StartTime = 100 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
var addedObject2 = new HitCircle
{
StartTime = 100,
Position = new Vector2(100),
};
AddStep("add one more hitobject", () => EditorBeatmap.Add(addedObject2));
AddAssert("selection unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
moveMouseToObject(() => addedObject2);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject2);
}
[Test]
public void TestMultiSelect()
{
var addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
moveMouseToObject(() => addedObjects[0]);
AddStep("click first", () => InputManager.Click(MouseButton.Left));
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[0]);
AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft));
moveMouseToObject(() => addedObjects[1]);
AddStep("click second", () => InputManager.Click(MouseButton.Left));
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
moveMouseToObject(() => addedObjects[2]);
AddStep("click third", () => InputManager.Click(MouseButton.Left));
AddAssert("3 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 3 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[2]));
moveMouseToObject(() => addedObjects[1]);
AddStep("click second", () => InputManager.Click(MouseButton.Left));
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
}
[TestCase(false)]
[TestCase(true)]
public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag)
{
HitCircle[] addedObjects = null;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
}));
moveMouseToObject(() => addedObjects[0]);
AddStep("click first", () => InputManager.Click(MouseButton.Left));
AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft));
moveMouseToObject(() => addedObjects[1]);
if (alreadySelectedBeforeDrag)
AddStep("click second", () => InputManager.Click(MouseButton.Left));
AddStep("mouse down on second", () => InputManager.PressButton(MouseButton.Left));
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
AddStep("drag to centre", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre));
AddAssert("positions changed", () => addedObjects[0].Position != Vector2.Zero && addedObjects[1].Position != new Vector2(50));
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
}
[Test]
public void TestBasicDeselect()
{
var addedObject = new HitCircle { StartTime = 100 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
AddStep("click away", () =>
{
InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre);
InputManager.Click(MouseButton.Left);
});
AddAssert("selection lost", () => EditorBeatmap.SelectedHitObjects.Count == 0);
}
[Test]
public void TestQuickDeleteRemovesObjectInPlacement()
{
var addedObject = new HitCircle
{
StartTime = 0,
Position = OsuPlayfield.BASE_SIZE * 0.5f
};
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("enter placement mode", () => InputManager.PressKey(Key.Number2));
moveMouseToObject(() => addedObject);
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestQuickDeleteRemovesObjectInSelection()
{
var addedObject = new HitCircle
{
StartTime = 0,
Position = OsuPlayfield.BASE_SIZE * 0.5f
};
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
moveMouseToObject(() => addedObject);
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestQuickDeleteRemovesSliderControlPoint()
{
Slider slider = null;
PathControlPoint[] points =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(50, 0)),
new PathControlPoint(new Vector2(100, 0))
};
AddStep("add slider", () =>
{
slider = new Slider
{
StartTime = 1000,
Path = new SliderPath(points)
};
EditorBeatmap.Add(slider);
});
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to controlpoint", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2);
AddStep("right click", () => InputManager.Click(MouseButton.Right));
// second click should nuke the object completely.
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
}
}

View File

@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osuTK;
@ -13,16 +14,29 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
{
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
Add(new SummaryTimeline
public TestSceneEditorSummaryTimeline()
{
editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("create timeline", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 50)
// required for track
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Add(new SummaryTimeline
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 50)
});
});
}
}

View File

@ -0,0 +1,55 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Input;
using static osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneTimelineHitObjectBlueprint : TimelineTestScene
{
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(Composer);
[Test]
public void TestDisallowZeroDurationObjects()
{
DragArea dragArea;
AddStep("add spinner", () =>
{
EditorBeatmap.Clear();
EditorBeatmap.Add(new Spinner
{
Position = new Vector2(256, 256),
StartTime = 2700,
Duration = 500
});
});
AddStep("hold down drag bar", () =>
{
// distinguishes between the actual drag bar and its "underlay shadow".
dragArea = this.ChildrenOfType<DragArea>().Single(bar => bar.HandlePositionalInput);
InputManager.MoveMouseTo(dragArea);
InputManager.PressButton(MouseButton.Left);
});
AddStep("try to drag bar past start", () =>
{
var blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().Single();
InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft - new Vector2(100, 0));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType<IHasDuration>().Single().Duration > 0);
}
}
}

View File

@ -23,22 +23,24 @@ namespace osu.Game.Tests.Visual.Editing
protected HitObjectComposer Composer { get; private set; }
protected EditorBeatmap EditorBeatmap { get; private set; }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
var editorBeatmap = new EditorBeatmap(playable);
EditorBeatmap = new EditorBeatmap(playable);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
Dependencies.Cache(EditorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(EditorBeatmap);
Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0);
AddRange(new Drawable[]
{
editorBeatmap,
EditorBeatmap,
Composer,
new FillFlowContainer
{
@ -51,17 +53,21 @@ namespace osu.Game.Tests.Visual.Editing
new AudioVisualiser(),
}
},
TimelineArea = new TimelineArea
TimelineArea = new TimelineArea(CreateTestComponent())
{
Child = CreateTestComponent(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.8f, 100),
}
});
}
protected override void LoadComplete()
{
base.LoadComplete();
Clock.Seek(2500);
}
public abstract Drawable CreateTestComponent();
private class AudioVisualiser : CompositeDrawable
@ -108,8 +114,6 @@ namespace osu.Game.Tests.Visual.Editing
[Resolved]
private EditorClock editorClock { get; set; }
private bool started;
public StartStopButton()
{
BackgroundColour = Color4.SlateGray;
@ -121,18 +125,17 @@ namespace osu.Game.Tests.Visual.Editing
private void onClick()
{
if (started)
{
if (editorClock.IsRunning)
editorClock.Stop();
Text = "Start";
}
else
{
editorClock.Start();
Text = "Stop";
}
}
started = !started;
protected override void Update()
{
base.Update();
Text = editorClock.IsRunning ? "Stop" : "Start";
}
}
}

View File

@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekToBreak(int breakIndex)
{
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime);
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
}

View File

@ -1,47 +1,35 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneComboCounter : SkinnableTestScene
{
private IEnumerable<SkinnableComboCounter> comboCounters => CreatedDrawables.OfType<SkinnableComboCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var comboCounter = new SkinnableComboCounter();
comboCounter.Current.Value = 1;
return comboCounter;
}));
AddStep("Create combo counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ComboCounter))));
}
[Test]
public void TestComboCounterIncrementing()
{
AddRepeatStep("increase combo", () =>
{
foreach (var counter in comboCounters)
counter.Current.Value++;
}, 10);
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
AddStep("reset combo", () =>
{
foreach (var counter in comboCounters)
counter.Current.Value = 0;
});
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
}
}

View File

@ -10,6 +10,8 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Storyboards;
using osuTK;
@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
cancel();
complete();
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).ResultsCreated);
}
/// <summary>
@ -84,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
// wait to ensure there was no attempt of pushing the results screen.
AddWaitStep("wait", resultsDisplayWaitCount);
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).ResultsCreated);
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
@ -110,16 +112,18 @@ namespace osu.Game.Tests.Visual.Gameplay
public class FakeRankingPushPlayer : TestPlayer
{
public bool GotoRankingInvoked;
public bool ResultsCreated { get; private set; }
public FakeRankingPushPlayer()
: base(true, true)
{
}
protected override void GotoRanking()
protected override ResultsScreen CreateResults(ScoreInfo score)
{
GotoRankingInvoked = true;
var results = base.CreateResults(score);
ResultsCreated = true;
return results;
}
}
}

View File

@ -1,9 +1,12 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
@ -20,24 +23,28 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private OsuConfigManager config { get; set; }
[SetUpSteps]
public void SetUpSteps()
private void create(HealthProcessor healthProcessor)
{
AddStep("create layer", () =>
{
Child = layer = new FailingLayer();
layer.BindHealthProcessor(new DrainingHealthProcessor(1));
Child = new HealthProcessorContainer(healthProcessor)
{
RelativeSizeAxes = Axes.Both,
Child = layer = new FailingLayer()
};
layer.ShowHealth.BindTo(showHealth);
});
AddStep("show health", () => showHealth.Value = true);
AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer is visible", () => layer.IsPresent);
AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
}
[Test]
public void TestLayerFading()
{
create(new DrainingHealthProcessor(0));
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
{
if (layer != null)
@ -45,15 +52,17 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f);
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
AddStep("set health to 1", () => layer.Current.Value = 1f);
AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent);
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
}
[Test]
public void TestLayerDisabledViaConfig()
{
AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
create(new DrainingHealthProcessor(0));
AddUntilStep("layer is visible", () => layer.IsPresent);
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
@ -61,7 +70,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestLayerVisibilityWithAccumulatingProcessor()
{
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1)));
create(new AccumulatingHealthProcessor(1));
AddUntilStep("layer is not visible", () => !layer.IsPresent);
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
@ -69,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestLayerVisibilityWithDrainingProcessor()
{
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1)));
create(new DrainingHealthProcessor(0));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddWaitStep("wait for potential fade", 10);
AddAssert("layer is still visible", () => layer.IsPresent);
@ -78,23 +88,36 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestLayerVisibilityWithDifferentOptions()
{
create(new DrainingHealthProcessor(0));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("don't show health", () => showHealth.Value = false);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("don't show health", () => showHealth.Value = false);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is visible", () => layer.IsPresent);
}
private class HealthProcessorContainer : Container
{
[Cached(typeof(HealthProcessor))]
private readonly HealthProcessor healthProcessor;
public HealthProcessorContainer(HealthProcessor healthProcessor)
{
this.healthProcessor = healthProcessor;
}
}
}
}

View File

@ -0,0 +1,104 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneGameplayLeaderboard : OsuTestScene
{
private readonly TestGameplayLeaderboard leaderboard;
private readonly BindableDouble playerScore = new BindableDouble();
public TestSceneGameplayLeaderboard()
{
Add(leaderboard = new TestGameplayLeaderboard
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(2),
});
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("reset leaderboard", () =>
{
leaderboard.Clear();
playerScore.Value = 1222333;
});
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
}
[Test]
public void TestPlayerScore()
{
var player2Score = new BindableDouble(1234567);
var player3Score = new BindableDouble(1111111);
AddStep("add player 2", () => createLeaderboardScore(player2Score, new User { Username = "Player 2" }));
AddStep("add player 3", () => createLeaderboardScore(player3Score, new User { Username = "Player 3" }));
AddUntilStep("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1));
AddUntilStep("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2));
AddUntilStep("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
AddStep("set score above player 3", () => player2Score.Value = playerScore.Value - 500);
AddUntilStep("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
AddUntilStep("is player 2 position #2", () => leaderboard.CheckPositionByUsername("Player 2", 2));
AddUntilStep("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
AddStep("set score below players", () => player2Score.Value = playerScore.Value - 123456);
AddUntilStep("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
AddUntilStep("is player 3 position #2", () => leaderboard.CheckPositionByUsername("Player 3", 2));
AddUntilStep("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3));
}
[Test]
public void TestRandomScores()
{
int playerNumber = 1;
AddRepeatStep("add player with random score", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 10);
}
[Test]
public void TestExistingUsers()
{
AddStep("add peppy", () => createRandomScore(new User { Username = "peppy", Id = 2 }));
AddStep("add smoogipoo", () => createRandomScore(new User { Username = "smoogipoo", Id = 1040328 }));
AddStep("add flyte", () => createRandomScore(new User { Username = "flyte", Id = 3103765 }));
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
}
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
{
var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
leaderboardScore.TotalScore.BindTo(score);
}
private class TestGameplayLeaderboard : GameplayLeaderboard
{
public bool CheckPositionByUsername(string username, int? expectedPosition)
{
var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
}
}
}
}

View File

@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@ -20,14 +19,14 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
PoolableSkinnableSample[] samples = null;
ISamplePlaybackDisabler sampleDisabler = null;
AddUntilStep("get variables", () =>
{
sampleDisabler = Player;
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).FirstOrDefault();
samples = slider?.ChildrenOfType<DrawableSample>().ToArray();
samples = slider?.ChildrenOfType<PoolableSkinnableSample>().ToArray();
return slider != null;
});

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osuTK.Input;
@ -19,6 +20,12 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private HUDOverlay hudOverlay;
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
@ -31,9 +38,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10);
AddRepeatStep("increase combo", () => { scoreProcessor.Combo.Value++; }, 10);
AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; });
AddStep("reset combo", () => { scoreProcessor.Combo.Value = 0; });
}
[Test]
@ -81,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@ -91,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue));
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test]
@ -120,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set keycounter visible false", () =>
{
config.Set<bool>(OsuSetting.KeyOverlay, false);
config.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
@ -132,19 +139,19 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.Set<bool>(OsuSetting.KeyOverlay, keyCounterVisibleValue));
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
private void createNew(Action<HUDOverlay> action = null)
{
AddStep("create overlay", () =>
{
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
hudOverlay.ComboCounter.Current.Value = 1;
scoreProcessor.Combo.Value = 1;
action?.Invoke(hudOverlay);

View File

@ -1,32 +1,39 @@
// 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.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using osu.Game.Rulesets.Judgements;
using osu.Framework.Utils;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHitErrorMeter : OsuTestScene
{
private BarHitErrorMeter barMeter;
private BarHitErrorMeter barMeter2;
private BarHitErrorMeter barMeter3;
private ColourHitErrorMeter colourMeter;
private ColourHitErrorMeter colourMeter2;
private ColourHitErrorMeter colourMeter3;
private HitWindows hitWindows;
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[Cached(typeof(DrawableRuleset))]
private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset();
public TestSceneHitErrorMeter()
{
@ -34,8 +41,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("New random judgement", () => newJudgement(), 40);
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
AddStep("Judgement barrage", () =>
@ -85,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
{
this.hitWindows = hitWindows;
hitWindows?.SetDifficulty(overallDifficulty);
drawableRuleset.HitWindows = hitWindows;
Clear();
Add(new FillFlowContainer
@ -105,40 +112,40 @@ namespace osu.Game.Tests.Visual.Gameplay
}
});
Add(barMeter = new BarHitErrorMeter(hitWindows, true)
Add(new BarHitErrorMeter
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
});
Add(barMeter2 = new BarHitErrorMeter(hitWindows, false)
Add(new BarHitErrorMeter
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
});
Add(barMeter3 = new BarHitErrorMeter(hitWindows, true)
Add(new BarHitErrorMeter
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.CentreLeft,
Rotation = 270,
});
Add(colourMeter = new ColourHitErrorMeter(hitWindows)
Add(new ColourHitErrorMeter
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 50 }
});
Add(colourMeter2 = new ColourHitErrorMeter(hitWindows)
Add(new ColourHitErrorMeter
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 50 }
});
Add(colourMeter3 = new ColourHitErrorMeter(hitWindows)
Add(new ColourHitErrorMeter
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.CentreLeft,
@ -149,18 +156,47 @@ namespace osu.Game.Tests.Visual.Gameplay
private void newJudgement(double offset = 0)
{
var judgement = new JudgementResult(new HitObject(), new Judgement())
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement())
{
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
Type = HitResult.Perfect,
};
});
}
barMeter.OnNewJudgement(judgement);
barMeter2.OnNewJudgement(judgement);
barMeter3.OnNewJudgement(judgement);
colourMeter.OnNewJudgement(judgement);
colourMeter2.OnNewJudgement(judgement);
colourMeter3.OnNewJudgement(judgement);
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
private class TestDrawableRuleset : DrawableRuleset
{
public HitWindows HitWindows;
public override IEnumerable<HitObject> Objects => new[] { new HitCircle { HitWindows = HitWindows } };
public override event Action<JudgementResult> NewResult;
public override event Action<JudgementResult> RevertResult;
public override Playfield Playfield { get; }
public override Container Overlays { get; }
public override Container FrameStableComponents { get; }
public override IFrameStableClock FrameStableClock { get; }
public override IReadOnlyList<Mod> Mods { get; }
public override double GameplayStartTime { get; }
public override GameplayCursorContainer Cursor { get; }
public TestDrawableRuleset()
: base(new OsuRuleset())
{
// won't compile without this.
NewResult?.Invoke(null);
RevertResult?.Invoke(null);
}
public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();
public override void SetRecordTarget(Score score) => throw new NotImplementedException();
public override void RequestResume(Action continueResume) => throw new NotImplementedException();
public override void CancelResume() => throw new NotImplementedException();
}
}
}

View File

@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(0, 0)]
[TestCase(-1000, -1000)]
[TestCase(-10000, -10000)]
public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
@ -64,6 +65,43 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
[TestCase(1000, 0, false)]
[TestCase(0, 0, false)]
[TestCase(-1000, -1000, false)]
[TestCase(-10000, -10000, false)]
[TestCase(1000, 0, true)]
[TestCase(0, 0, true)]
[TestCase(-1000, -1000, true)]
[TestCase(-10000, -10000, true)]
public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
// these should be ignored as we have an alpha visibility blocker proceeding this command.
sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var loopGroup = sprite.AddLoop(-20000, 50);
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future.
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
AddStep("create player", () =>
@ -92,9 +130,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
protected override void UpdateAfterChildren()
protected override void Update()
{
base.UpdateAfterChildren();
base.Update();
if (!FirstFrameClockTime.HasValue)
{

View File

@ -56,9 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm();
resume();
confirmClockRunning(false);
confirmPauseOverlayShown(false);
confirmPausedWithNoOverlay();
AddStep("click to resume", () => InputManager.Click(MouseButton.Left));
confirmClockRunning(true);
@ -71,15 +69,14 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
confirmClockRunning(false);
confirmPauseOverlayShown(false);
confirmPausedWithNoOverlay();
pauseAndConfirm();
AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden);
confirmPaused();
confirmNotExited();
}
[Test]
@ -94,33 +91,54 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[Test]
public void TestPauseTooSoon()
public void TestUserPauseWhenPauseNotAllowed()
{
AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
pauseFromUserExitKey();
confirmExited();
}
[Test]
public void TestUserPauseDuringCooldownTooSoon()
{
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
pauseAndConfirm();
resume();
pause();
pauseFromUserExitKey();
confirmClockRunning(true);
confirmPauseOverlayShown(false);
confirmResumed();
confirmNotExited();
}
[Test]
public void TestExitTooSoon()
public void TestQuickExitDuringCooldownTooSoon()
{
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
pauseAndConfirm();
resume();
AddStep("pause via exit key", () => Player.ExitViaQuickExit());
confirmResumed();
AddAssert("exited", () => !Player.IsCurrentScreen());
}
[Test]
public void TestExitSoonAfterResumeSucceeds()
{
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resume();
AddStep("exit too soon", () => Player.Exit());
AddStep("exit quick", () => Player.Exit());
confirmClockRunning(true);
confirmPauseOverlayShown(false);
AddAssert("not exited", () => Player.IsCurrentScreen());
confirmResumed();
AddAssert("exited", () => !Player.IsCurrentScreen());
}
[Test]
@ -131,22 +149,37 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false);
pause();
confirmClockRunning(false);
confirmPauseOverlayShown(false);
AddStep("pause via forced pause", () => Player.Pause());
confirmPausedWithNoOverlay();
AddAssert("fail overlay still shown", () => Player.FailOverlayVisible);
exitAndConfirm();
}
[Test]
public void TestExitFromFailedGameplay()
public void TestExitFromFailedGameplayAfterFailAnimation()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);
AddStep("exit via user pause", () => Player.ExitViaPause());
confirmExited();
}
[Test]
public void TestExitFromFailedGameplayDuringFailAnimation()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
// will finish the fail animation and show the fail/pause screen.
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
// will actually exit.
AddStep("exit via pause key", () => Player.ExitViaPause());
confirmExited();
}
@ -245,7 +278,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void pauseAndConfirm()
{
pause();
pauseFromUserExitKey();
confirmPaused();
}
@ -257,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void exitAndConfirm()
{
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
confirmNotExited();
AddStep("exit", () => Player.Exit());
confirmExited();
confirmNoTrackAdjustments();
@ -266,7 +299,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void confirmPaused()
{
confirmClockRunning(false);
AddAssert("player not exited", () => Player.IsCurrentScreen());
confirmNotExited();
AddAssert("player not failed", () => !Player.HasFailed);
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
}
@ -277,18 +310,22 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmPauseOverlayShown(false);
}
private void confirmExited()
private void confirmPausedWithNoOverlay()
{
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
confirmClockRunning(false);
confirmPauseOverlayShown(false);
}
private void confirmExited() => AddUntilStep("player exited", () => !Player.IsCurrentScreen());
private void confirmNotExited() => AddAssert("player not exited", () => Player.IsCurrentScreen());
private void confirmNoTrackAdjustments()
{
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
}
private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause());
private void resume() => AddStep("resume", () => Player.Resume());
private void confirmPauseOverlayShown(bool isShown) =>
@ -307,6 +344,10 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
public void ExitViaPause() => PerformExit(true);
public void ExitViaQuickExit() => PerformExit(false);
public override void OnEntering(IScreen last)
{
base.OnEntering(last);

View File

@ -1,6 +1,7 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -8,21 +9,17 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : OsuPlayerTestScene
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
return beatmap;
}
[Resolved]
private GameHost host { get; set; }
@ -33,10 +30,57 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
}
/// <summary>
/// Tests that if a pause from focus lose is performed while in pause cooldown,
/// the player will still pause after the cooldown is finished.
/// </summary>
[Test]
public void TestPauseWhileInCooldown()
{
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddStep("set active", () => ((Bindable<bool>)host.IsActive).Value = true);
AddStep("resume player", () => Player.Resume());
AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value);
bool pauseCooldownActive = false;
AddStep("set inactive again", () =>
{
pauseCooldownActive = Player.PauseCooldownActive;
((Bindable<bool>)host.IsActive).Value = false;
});
AddAssert("pause cooldown active", () => pauseCooldownActive);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
return new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 30000 },
new HitCircle { StartTime = 35000 },
},
};
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new TestWorkingBeatmap(beatmap, storyboard, Audio);
}
}

View File

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Utils;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private readonly VolumeOverlay volumeOverlay;
[Cached(typeof(BatteryInfo))]
private readonly LocalBatteryInfo batteryInfo = new LocalBatteryInfo();
private readonly ChangelogOverlay changelogOverlay;
public TestScenePlayerLoader()
@ -288,6 +292,33 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
[TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
[TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
[TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
batteryInfo.SetCharging(isCharging);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0));
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for player load", () => player.IsLoaded);
}
[Test]
public void TestEpilepsyWarningEarlyExit()
{
@ -321,6 +352,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override string Name => string.Empty;
public override string Acronym => string.Empty;
public override double ScoreMultiplier => 1;
public override string Description => string.Empty;
public bool Applied { get; private set; }
@ -348,5 +380,29 @@ namespace osu.Game.Tests.Visual.Gameplay
throw new TimeoutException();
}
}
/// <summary>
/// Mutable dummy BatteryInfo class for <see cref="TestScenePlayerLoader.TestLowBatteryNotification"/>
/// </summary>
/// <inheritdoc/>
private class LocalBatteryInfo : BatteryInfo
{
private bool isCharging = true;
private double chargeLevel = 1;
public override bool IsCharging => isCharging;
public override double ChargeLevel => chargeLevel;
public void SetCharging(bool value)
{
isCharging = value;
}
public void SetChargeLevel(double value)
{
chargeLevel = value;
}
}
}
}

View File

@ -17,10 +17,12 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osuTK;
using osuTK.Graphics;
@ -129,6 +131,31 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
}
[Test]
public void TestApplyHitResultOnKilled()
{
ManualClock clock = null;
bool anyJudged = false;
void onNewResult(JudgementResult _) => anyJudged = true;
var beatmap = new Beatmap();
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
AddStep("subscribe to new result", () =>
{
anyJudged = false;
drawableRuleset.NewResult += onNewResult;
});
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
AddAssert("object judged", () => anyJudged);
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
}
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null) => AddStep("create test", () =>
{
var ruleset = new TestPoolingRuleset();
@ -192,6 +219,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load()
{
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
@ -220,19 +248,30 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
{
yield return new TestHitObject
switch (original)
{
StartTime = original.StartTime,
Duration = 250
};
case TestKilledHitObject h:
yield return h;
break;
default:
yield return new TestHitObject
{
StartTime = original.StartTime,
Duration = 250
};
break;
}
}
}
#endregion
#region HitObject
#region HitObjects
private class TestHitObject : ConvertHitObject
private class TestHitObject : ConvertHitObject, IHasDuration
{
public double EndTime => StartTime + Duration;
@ -287,6 +326,30 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
private class TestKilledHitObject : TestHitObject
{
}
private class DrawableTestKilledHitObject : DrawableHitObject<TestKilledHitObject>
{
public DrawableTestKilledHitObject()
: base(null)
{
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
base.UpdateHitStateTransforms(state);
Expire();
}
public override void OnKilled()
{
base.OnKilled();
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
}
#endregion
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap));
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap, Array.Empty<Mod>()));
}
protected override void AddCheckSteps()

View File

@ -19,6 +19,7 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(replay)
Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay })
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
@ -117,8 +118,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestBasic()
{
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddUntilStep("one frame recorded", () => replay.Frames.Count == 1);
AddAssert("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
AddUntilStep("at least one frame recorded", () => replay.Frames.Count > 0);
AddUntilStep("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
}
[Test]
@ -138,14 +139,16 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestLimitedFrameRate()
{
ScheduledDelegate moveFunction = null;
int initialFrameCount = 0;
AddStep("lower rate", () => recorder.RecordFrameRate = 2);
AddStep("count frames", () => initialFrameCount = replay.Frames.Count);
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10);
AddAssert("less than 10 frames recorded", () => replay.Frames.Count - initialFrameCount < 10);
}
[Test]
@ -243,7 +246,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
public override IEnumerable<IKeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
@ -271,7 +274,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
public TestReplayRecorder(Score target)
: base(target)
{
}

View File

@ -15,6 +15,7 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
@ -44,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = new TestReplayRecorder(replay)
Recorder = new TestReplayRecorder(new Score { Replay = replay })
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
},
@ -178,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
public override IEnumerable<IKeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
@ -206,7 +207,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
public TestReplayRecorder(Score target)
: base(target)
{
}

View File

@ -0,0 +1,45 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinEditor : PlayerTestScene
{
private SkinEditor skinEditor;
[Resolved]
private SkinManager skinManager { get; set; }
protected override bool Autoplay => true;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("reload skin editor", () =>
{
skinEditor?.Expire();
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
});
}
[Test]
public void TestToggleEditor()
{
AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
}
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -0,0 +1,26 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinEditorComponentsList : SkinnableTestScene
{
[Test]
public void TestToggleEditor()
{
AddStep("show available components", () => SetContents(() => new SkinComponentToolbox(300)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}));
}
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}

View File

@ -0,0 +1,65 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Skinning.Editor;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene
{
[Cached]
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor();
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create editor overlay", () =>
{
SetContents(() =>
{
var ruleset = new OsuRuleset();
var mods = new[] { ruleset.GetAutoplayMod() };
var working = CreateWorkingBeatmap(ruleset.RulesetInfo);
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods);
var hudOverlay = new HUDOverlay(drawableRuleset, mods)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
scoreProcessor.Combo.Value = 1;
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
drawableRuleset,
hudOverlay,
new SkinEditor(hudOverlay),
}
};
});
});
}
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}

View File

@ -1,49 +1,36 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene
{
private IEnumerable<SkinnableAccuracyCounter> accuracyCounters => CreatedDrawables.OfType<SkinnableAccuracyCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var accuracyCounter = new SkinnableAccuracyCounter();
accuracyCounter.Current.Value = 1;
return accuracyCounter;
}));
AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1);
AddStep("Create accuracy counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter))));
}
[Test]
public void TestChangingAccuracy()
{
AddStep(@"Reset all", delegate
{
foreach (var s in accuracyCounters)
s.Current.Value = 1;
});
AddStep(@"Reset all", () => scoreProcessor.Accuracy.Value = 1);
AddStep(@"Hit! :D", delegate
{
foreach (var s in accuracyCounters)
s.Current.Value -= 0.023f;
});
AddStep(@"Miss :(", () => scoreProcessor.Accuracy.Value -= 0.023);
}
}
}

View File

@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
@ -309,7 +309,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();

View File

@ -14,6 +14,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osuTK.Input;
@ -23,6 +24,12 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private HUDOverlay hudOverlay;
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
// best way to check without exposing.
@ -37,17 +44,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
AddRepeatStep("increase combo", () =>
{
foreach (var hud in hudOverlays)
hud.ComboCounter.Current.Value++;
}, 10);
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
AddStep("reset combo", () =>
{
foreach (var hud in hudOverlays)
hud.ComboCounter.Current.Value = 0;
});
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
[Test]
@ -80,13 +79,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
SetContents(() =>
{
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
hudOverlay.ComboCounter.Current.Value = 1;
action?.Invoke(hudOverlay);
return hudOverlay;

View File

@ -1,35 +1,33 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Screens.Play;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableHealthDisplay : SkinnableTestScene
{
private IEnumerable<SkinnableHealthDisplay> healthDisplays => CreatedDrawables.OfType<SkinnableHealthDisplay>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create health displays", () =>
{
SetContents(() => new SkinnableHealthDisplay());
});
AddStep("Create health displays", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.HealthDisplay))));
AddStep(@"Reset all", delegate
{
foreach (var s in healthDisplays)
s.Current.Value = 1;
healthProcessor.Health.Value = 1;
});
}
@ -38,23 +36,21 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddRepeatStep(@"decrease hp", delegate
{
foreach (var healthDisplay in healthDisplays)
healthDisplay.Current.Value -= 0.08f;
healthProcessor.Health.Value -= 0.08f;
}, 10);
AddRepeatStep(@"increase hp without flash", delegate
{
foreach (var healthDisplay in healthDisplays)
healthDisplay.Current.Value += 0.1f;
healthProcessor.Health.Value += 0.1f;
}, 3);
AddRepeatStep(@"increase hp with flash", delegate
{
foreach (var healthDisplay in healthDisplays)
healthProcessor.Health.Value += 0.1f;
healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement())
{
healthDisplay.Current.Value += 0.1f;
healthDisplay.Flash(new JudgementResult(null, new OsuJudgement()));
}
Type = HitResult.Perfect
});
}, 3);
}
}

View File

@ -1,54 +1,41 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableScoreCounter : SkinnableTestScene
{
private IEnumerable<SkinnableScoreCounter> scoreCounters => CreatedDrawables.OfType<SkinnableScoreCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var comboCounter = new SkinnableScoreCounter();
comboCounter.Current.Value = 1;
return comboCounter;
}));
AddStep("Create score counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ScoreCounter))));
}
[Test]
public void TestScoreCounterIncrementing()
{
AddStep(@"Reset all", delegate
{
foreach (var s in scoreCounters)
s.Current.Value = 0;
});
AddStep(@"Reset all", () => scoreProcessor.TotalScore.Value = 0);
AddStep(@"Hit! :D", delegate
{
foreach (var s in scoreCounters)
s.Current.Value += 300;
});
AddStep(@"Hit! :D", () => scoreProcessor.TotalScore.Value += 300);
}
[Test]
public void TestVeryLargeScore()
{
AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000));
AddStep("set large score", () => scoreProcessor.TotalScore.Value = 1_000_000_000);
}
}
}

View File

@ -43,70 +43,60 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestStoppedSoundDoesntResumeAfterPause()
{
DrawableSample sample = null;
AddStep("start sample with looping", () =>
{
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
skinnableSound.Looping = true;
skinnableSound.Play();
});
AddUntilStep("wait for sample to start playing", () => sample.Playing);
AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying);
AddStep("stop sample", () => skinnableSound.Stop());
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddWaitStep("wait a bit", 5);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
}
[Test]
public void TestLoopingSoundResumesAfterPause()
{
DrawableSample sample = null;
AddStep("start sample with looping", () =>
{
skinnableSound.Looping = true;
skinnableSound.Play();
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
});
AddUntilStep("wait for sample to start playing", () => sample.Playing);
AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddUntilStep("wait for sample to start playing", () => sample.Playing);
AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying);
}
[Test]
public void TestNonLoopingStopsWithPause()
{
DrawableSample sample = null;
AddStep("start sample", () =>
{
skinnableSound.Play();
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
});
AddStep("start sample", () => skinnableSound.Play());
AddAssert("sample playing", () => sample.Playing);
AddAssert("sample playing", () => skinnableSound.IsPlaying);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("sample not playing", () => !sample.Playing);
AddUntilStep("sample not playing", () => !skinnableSound.IsPlaying);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
}
[Test]
@ -119,10 +109,10 @@ namespace osu.Game.Tests.Visual.Gameplay
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
});
AddAssert("sample playing", () => sample.Playing);
AddAssert("sample playing", () => skinnableSound.IsPlaying);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying);
AddStep("trigger skin change", () => skinSource.TriggerSourceChanged());
@ -133,11 +123,11 @@ namespace osu.Game.Tests.Visual.Gameplay
return sample != oldSample;
});
AddAssert("new sample stopped", () => !sample.Playing);
AddAssert("new sample stopped", () => !skinnableSound.IsPlaying);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddWaitStep("wait a bit", 5);
AddAssert("new sample not played", () => !sample.Playing);
AddAssert("new sample not played", () => !skinnableSound.IsPlaying);
}
[Cached(typeof(ISkinSource))]
@ -155,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
public void TriggerSourceChanged()

View File

@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
working.LoadTrack();
Child = gameplayClockContainer = new GameplayClockContainer(working, 0)
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () =>
{
increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2;
increment = skip_time - gameplayClock.CurrentTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME / 2;
InputManager.Click(MouseButton.Left);
});
AddStep("click", () => InputManager.Click(MouseButton.Left));

Some files were not shown because too many files have changed in this diff Show More