using BallanceTasEditor.Backend; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BallanceTasEditorTests.Backend { [TestClass] public class TasOperationTests { #region Utilities private static IEnumerable TestDataCombiner(IEnumerable seqs, IEnumerable payloads) where T : notnull { // YYC MARK: // We must iterate payload as outside "for" and iterate sequence as inner "for", // because if we flip this order, each payload will share the same reference to sequence, // which cause test error. foreach (T payload in payloads) { foreach (ITasSequence seq in seqs) { yield return new object[] { seq, payload }; } } } private static void AssertRevocableOperation(ITasSequence sequence, ITasRevocableOperation op, string source, string expected) { // Prepare environment sequence.Clear(); GenerateSequence(sequence, source); // Test execute op.Execute(sequence); Assert.AreEqual(expected, SummarizeSequence(sequence)); // Test revoke op.Revoke(sequence); Assert.AreEqual(source, SummarizeSequence(sequence)); // Test re-execute and re-revoke again op.Execute(sequence); Assert.AreEqual(expected, SummarizeSequence(sequence)); op.Revoke(sequence); Assert.AreEqual(source, SummarizeSequence(sequence)); } private static void AssertOperation(ITasSequence sequence, ITasOperation op, string source, string expected) { // Prepare environment sequence.Clear(); GenerateSequence(sequence, source); // Test execute op.Execute(sequence); Assert.AreEqual(expected, SummarizeSequence(sequence)); } private static void GenerateSequence(ITasSequence sequence, string pattern) { var strFrame = pattern.Split(';'); var frameIter = strFrame.Select((s) => { // Extract FPS and Keys var framePair = s.Split(','); var fps = uint.Parse(framePair[0]); var keyFlags = uint.Parse(framePair[1]); // Build raw frame and convert it to frame. var rawFrame = new RawTasFrame() { TimeDelta = FpsConverter.ToDelta(fps), KeyFlags = keyFlags }; return TasFrame.FromRaw(rawFrame); }); var frameExactSizeIter = new ExactSizeEnumerableAdapter(frameIter, strFrame.Length); sequence.Insert(0, frameExactSizeIter); } private static string SummarizeSequence(ITasSequence sequence) { return String.Join( ";", sequence.Select((f) => { var rawFrame = f.ToRaw(); return $"{FpsConverter.ToRoundFps(rawFrame.TimeDelta)},{rawFrame.KeyFlags}"; }) ); } #endregion #region Cell Keys Operation public record CellKeysOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } public required CellKeysOperationKind Kind { get; init; } public required int StartIndex { get; init; } public required int EndIndex { get; init; } public required TasKey StartKey { get; init; } public required TasKey EndKey { get; init; } } private static IEnumerable GetCellKeysOperationTestPayload() { yield return new CellKeysOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,15;1,15;1,15;1,4;1,5", Kind = CellKeysOperationKind.Set, StartIndex = 0, EndIndex = 2, StartKey = TasKey.KEY_UP, EndKey = TasKey.KEY_RIGHT }; yield return new CellKeysOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,0;1,0;1,0;1,4;1,5", Kind = CellKeysOperationKind.Unset, StartIndex = 0, EndIndex = 2, StartKey = TasKey.KEY_UP, EndKey = TasKey.KEY_RIGHT }; yield return new CellKeysOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,14;1,13;1,12;1,4;1,5", Kind = CellKeysOperationKind.Flip, StartIndex = 0, EndIndex = 2, StartKey = TasKey.KEY_UP, EndKey = TasKey.KEY_RIGHT }; } private static IEnumerable CellKeysOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetCellKeysOperationTestPayload()); } } /// /// CellKeysOperation测试。 /// [DataTestMethod] [DynamicData(nameof(CellKeysOperationTestDataProvider))] public void CellKeysOperationTest(ITasSequence sequence, CellKeysOperationTestPayload payload) { var op = CellKeysOperation.FromCellRange(payload.Kind, payload.StartIndex, payload.EndIndex, payload.StartKey, payload.EndKey); AssertRevocableOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Frame Fps Operation public record FrameFpsOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } public required int StartIndex { get; init; } public required int EndIndex { get; init; } public required uint Fps { get; init; } } private static IEnumerable GetFrameFpsOperationTestPayload() { yield return new FrameFpsOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "2,1;2,2;2,3;2,4;1,5", StartIndex = 0, EndIndex = 3, Fps = 2 }; yield return new FrameFpsOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;4,2;4,3;4,4;4,5", StartIndex = 1, EndIndex = 4, Fps = 4 }; } private static IEnumerable FrameFpsOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetFrameFpsOperationTestPayload()); } } /// /// FrameFpsOperation测试。 /// [DataTestMethod] [DynamicData(nameof(FrameFpsOperationTestDataProvider))] public void FrameFpsOperationTest(ITasSequence sequence, FrameFpsOperationTestPayload payload) { var op = FrameFpsOperation.FromFrameRange(payload.StartIndex, payload.EndIndex, payload.Fps); AssertRevocableOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Remove Frame Operation public record RemoveFrameOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } public required int StartIndex { get; init; } public required int EndIndex { get; init; } } private static IEnumerable GetRemoveFrameOperationTestPayload() { yield return new RemoveFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,3;1,4;1,5", StartIndex = 0, EndIndex = 1 }; yield return new RemoveFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "", StartIndex = 0, EndIndex = 4 }; yield return new RemoveFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;1,2;1,3", StartIndex = 3, EndIndex = 4 }; yield return new RemoveFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,2;1,3;1,4;1,5", StartIndex = 0, EndIndex = 0 }; yield return new RemoveFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;1,2;1,3;1,4", StartIndex = 4, EndIndex = 4 }; } private static IEnumerable RemoveFrameOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetRemoveFrameOperationTestPayload()); } } /// /// RemoveFrameOperation测试。 /// [DataTestMethod] [DynamicData(nameof(RemoveFrameOperationTestDataProvider))] public void RemoveFrameOperationTest(ITasSequence sequence, RemoveFrameOperationTestPayload payload) { var op = new RemoveFrameOperation(payload.StartIndex, payload.EndIndex); AssertRevocableOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Add Frame Operation public record AddFrameOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } public required AddFrameOperationKind Kind { get; init; } public required int Index { get; init; } public required uint Fps { get; init; } public required int Count { get; init; } } private static IEnumerable GetAddFrameOperationTestPayload() { yield return new AddFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;1,2;240,0;240,0;240,0;1,3;1,4;1,5", Kind = AddFrameOperationKind.Before, Index = 2, Fps = 240, Count = 3 }; yield return new AddFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;1,2;1,3;240,0;240,0;240,0;1,4;1,5", Kind = AddFrameOperationKind.After, Index = 2, Fps = 240, Count = 3 }; yield return new AddFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "240,0;240,0;240,0;1,1;1,2;1,3;1,4;1,5", Kind = AddFrameOperationKind.Before, Index = 0, Fps = 240, Count = 3 }; yield return new AddFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,1;1,2;1,3;1,4;1,5;240,0;240,0;240,0", Kind = AddFrameOperationKind.After, Index = 4, Fps = 240, Count = 3 }; } private static IEnumerable AddFrameOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetAddFrameOperationTestPayload()); } } /// /// AddFrameOperation测试。 /// [DataTestMethod] [DynamicData(nameof(AddFrameOperationTestDataProvider))] public void AddFrameOperationTest(ITasSequence sequence, AddFrameOperationTestPayload payload) { var op = new AddFrameOperation(payload.Kind, payload.Index, payload.Fps, payload.Count); AssertRevocableOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Insert Frame Operation public record InsertFrameOperationTestPayload { public required string Source { get; init; } public required string Inserted { get; init; } public required string Expected { get; init; } public required InsertFrameOperationPosition Position { get; init; } public required InsertFrameOperationMode Mode { get; init; } public required int Index { get; init; } } private static IEnumerable GetInsertFrameOperationTestPayload() { yield return new InsertFrameOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Inserted = "1,6;1,7;1,8;1,9;1,10", Expected = "1,1;1,2;2,1;2,2;1,3;1,4;1,5", Position = InsertFrameOperationPosition.After, Mode = InsertFrameOperationMode.Overwrite, Index = 0 }; } private static IEnumerable InsertFrameOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetInsertFrameOperationTestPayload()); } } /// /// InsertFrameOperation测试。 /// [DataTestMethod] [DynamicData(nameof(InsertFrameOperationTestDataProvider))] public void InsertFrameOperationTest(ITasSequence sequence, InsertFrameOperationTestPayload payload) { // YYC MARK: // I use a nasty way to extract "inserted" data from given string, // because we only support parsing pattern string into TAS sequence. GenerateSequence(sequence, payload.Inserted); var inserted = sequence.ToArray(); var insertedIter = new ExactSizeEnumerableAdapter(inserted, inserted.Length); sequence.Clear(); // Now we can test it var op = new InsertFrameOperation(payload.Position, payload.Mode, payload.Index, insertedIter); AssertRevocableOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Clear Keys Operation public record ClearKeysOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } } private static IEnumerable GetClearKeysOperationTestPayload() { yield return new ClearKeysOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "1,0;1,0;1,0;1,0;1,0" }; } private static IEnumerable ClearKeysOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetClearKeysOperationTestPayload()); } } /// /// ClearKeysOperation测试。 /// [DataTestMethod] [DynamicData(nameof(ClearKeysOperationTestDataProvider))] public void ClearKeysOperationTest(ITasSequence sequence, ClearKeysOperationTestPayload payload) { var op = new ClearKeysOperation(); AssertOperation(sequence, op, payload.Source, payload.Expected); } #endregion #region Uniform Fps Operation public record UniformFpsOperationTestPayload { public required string Source { get; init; } public required string Expected { get; init; } public required uint Fps { get; init; } } private static IEnumerable GetUniformFpsOperationTestPayload() { yield return new UniformFpsOperationTestPayload { Source = "1,1;1,2;1,3;1,4;1,5", Expected = "240,1;240,2;240,3;240,4;240,5", Fps = 240 }; } private static IEnumerable UniformFpsOperationTestDataProvider { get { return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetUniformFpsOperationTestPayload()); } } /// /// UniformFpsOperation测试。 /// [DataTestMethod] [DynamicData(nameof(UniformFpsOperationTestDataProvider))] public void UniformFpsOperationTest(ITasSequence sequence, UniformFpsOperationTestPayload payload) { var op = new UniformFpsOperation(payload.Fps); AssertOperation(sequence, op, payload.Source, payload.Expected); } #endregion } }