diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..9357d3b75c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,957 @@
+{
+    "Mappings": [{
+        "StartTime": 500.0,
+        "Objects": [{
+            "StartTime": 500.0,
+            "Position": 96.0
+        }, {
+            "StartTime": 562.0,
+            "Position": 100.84
+        }, {
+            "StartTime": 625.0,
+            "Position": 125.0
+        }, {
+            "StartTime": 687.0,
+            "Position": 152.84
+        }, {
+            "StartTime": 750.0,
+            "Position": 191.0
+        }, {
+            "StartTime": 812.0,
+            "Position": 212.84
+        }, {
+            "StartTime": 875.0,
+            "Position": 217.0
+        }, {
+            "StartTime": 937.0,
+            "Position": 234.84
+        }, {
+            "StartTime": 1000.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 1062.0,
+            "Position": 267.84
+        }, {
+            "StartTime": 1125.0,
+            "Position": 284.0
+        }, {
+            "StartTime": 1187.0,
+            "Position": 311.84
+        }, {
+            "StartTime": 1250.0,
+            "Position": 350.0
+        }, {
+            "StartTime": 1312.0,
+            "Position": 359.84
+        }, {
+            "StartTime": 1375.0,
+            "Position": 367.0
+        }, {
+            "StartTime": 1437.0,
+            "Position": 400.84
+        }, {
+            "StartTime": 1500.0,
+            "Position": 416.0
+        }, {
+            "StartTime": 1562.0,
+            "Position": 377.159973
+        }, {
+            "StartTime": 1625.0,
+            "Position": 367.0
+        }, {
+            "StartTime": 1687.0,
+            "Position": 374.159973
+        }, {
+            "StartTime": 1750.0,
+            "Position": 353.0
+        }, {
+            "StartTime": 1812.0,
+            "Position": 329.159973
+        }, {
+            "StartTime": 1875.0,
+            "Position": 288.0
+        }, {
+            "StartTime": 1937.0,
+            "Position": 259.159973
+        }, {
+            "StartTime": 2000.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 2058.0,
+            "Position": 232.44
+        }, {
+            "StartTime": 2116.0,
+            "Position": 222.879974
+        }, {
+            "StartTime": 2174.0,
+            "Position": 185.319992
+        }, {
+            "StartTime": 2232.0,
+            "Position": 177.76001
+        }, {
+            "StartTime": 2290.0,
+            "Position": 162.200012
+        }, {
+            "StartTime": 2348.0,
+            "Position": 158.639984
+        }, {
+            "StartTime": 2406.0,
+            "Position": 111.079994
+        }, {
+            "StartTime": 2500.0,
+            "Position": 96.0
+        }]
+    }, {
+        "StartTime": 3000.0,
+        "Objects": [{
+            "StartTime": 3000.0,
+            "Position": 18.0
+        }, {
+            "StartTime": 3062.0,
+            "Position": 482.0
+        }, {
+            "StartTime": 3125.0,
+            "Position": 243.0
+        }, {
+            "StartTime": 3187.0,
+            "Position": 332.0
+        }, {
+            "StartTime": 3250.0,
+            "Position": 477.0
+        }, {
+            "StartTime": 3312.0,
+            "Position": 376.0
+        }, {
+            "StartTime": 3375.0,
+            "Position": 104.0
+        }, {
+            "StartTime": 3437.0,
+            "Position": 156.0
+        }, {
+            "StartTime": 3500.0,
+            "Position": 135.0
+        }, {
+            "StartTime": 3562.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 3625.0,
+            "Position": 360.0
+        }, {
+            "StartTime": 3687.0,
+            "Position": 199.0
+        }, {
+            "StartTime": 3750.0,
+            "Position": 239.0
+        }, {
+            "StartTime": 3812.0,
+            "Position": 326.0
+        }, {
+            "StartTime": 3875.0,
+            "Position": 393.0
+        }, {
+            "StartTime": 3937.0,
+            "Position": 470.0
+        }, {
+            "StartTime": 4000.0,
+            "Position": 136.0
+        }]
+    }, {
+        "StartTime": 4500.0,
+        "Objects": [{
+            "StartTime": 4500.0,
+            "Position": 317.0
+        }, {
+            "StartTime": 4562.0,
+            "Position": 354.0
+        }, {
+            "StartTime": 4625.0,
+            "Position": 414.0
+        }, {
+            "StartTime": 4687.0,
+            "Position": 39.0
+        }, {
+            "StartTime": 4750.0,
+            "Position": 172.0
+        }, {
+            "StartTime": 4812.0,
+            "Position": 479.0
+        }, {
+            "StartTime": 4875.0,
+            "Position": 18.0
+        }, {
+            "StartTime": 4937.0,
+            "Position": 151.0
+        }, {
+            "StartTime": 5000.0,
+            "Position": 342.0
+        }, {
+            "StartTime": 5062.0,
+            "Position": 400.0
+        }, {
+            "StartTime": 5125.0,
+            "Position": 420.0
+        }, {
+            "StartTime": 5187.0,
+            "Position": 90.0
+        }, {
+            "StartTime": 5250.0,
+            "Position": 220.0
+        }, {
+            "StartTime": 5312.0,
+            "Position": 80.0
+        }, {
+            "StartTime": 5375.0,
+            "Position": 421.0
+        }, {
+            "StartTime": 5437.0,
+            "Position": 473.0
+        }, {
+            "StartTime": 5500.0,
+            "Position": 97.0
+        }]
+    }, {
+        "StartTime": 6000.0,
+        "Objects": [{
+            "StartTime": 6000.0,
+            "Position": 105.0
+        }, {
+            "StartTime": 6062.0,
+            "Position": 249.0
+        }, {
+            "StartTime": 6125.0,
+            "Position": 163.0
+        }, {
+            "StartTime": 6187.0,
+            "Position": 194.0
+        }, {
+            "StartTime": 6250.0,
+            "Position": 106.0
+        }, {
+            "StartTime": 6312.0,
+            "Position": 212.0
+        }, {
+            "StartTime": 6375.0,
+            "Position": 257.0
+        }, {
+            "StartTime": 6437.0,
+            "Position": 461.0
+        }, {
+            "StartTime": 6500.0,
+            "Position": 79.0
+        }]
+    }, {
+        "StartTime": 7000.0,
+        "Objects": [{
+            "StartTime": 7000.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 7062.0,
+            "Position": 294.84
+        }, {
+            "StartTime": 7125.0,
+            "Position": 279.0
+        }, {
+            "StartTime": 7187.0,
+            "Position": 309.84
+        }, {
+            "StartTime": 7250.0,
+            "Position": 336.0
+        }, {
+            "StartTime": 7312.0,
+            "Position": 322.16
+        }, {
+            "StartTime": 7375.0,
+            "Position": 308.0
+        }, {
+            "StartTime": 7437.0,
+            "Position": 263.16
+        }, {
+            "StartTime": 7500.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 7562.0,
+            "Position": 261.84
+        }, {
+            "StartTime": 7625.0,
+            "Position": 277.0
+        }, {
+            "StartTime": 7687.0,
+            "Position": 318.84
+        }, {
+            "StartTime": 7750.0,
+            "Position": 336.0
+        }, {
+            "StartTime": 7803.0,
+            "Position": 305.04
+        }, {
+            "StartTime": 7857.0,
+            "Position": 307.76
+        }, {
+            "StartTime": 7910.0,
+            "Position": 297.8
+        }, {
+            "StartTime": 8000.0,
+            "Position": 256.0
+        }]
+    }, {
+        "StartTime": 8500.0,
+        "Objects": [{
+            "StartTime": 8500.0,
+            "Position": 32.0
+        }, {
+            "StartTime": 8562.0,
+            "Position": 22.8515015
+        }, {
+            "StartTime": 8625.0,
+            "Position": 28.5659637
+        }, {
+            "StartTime": 8687.0,
+            "Position": 50.3433228
+        }, {
+            "StartTime": 8750.0,
+            "Position": 56.58974
+        }, {
+            "StartTime": 8812.0,
+            "Position": 64.23422
+        }, {
+            "StartTime": 8875.0,
+            "Position": 67.7117844
+        }, {
+            "StartTime": 8937.0,
+            "Position": 90.52607
+        }, {
+            "StartTime": 9000.0,
+            "Position": 101.81015
+        }, {
+            "StartTime": 9062.0,
+            "Position": 113.478188
+        }, {
+            "StartTime": 9125.0,
+            "Position": 159.414444
+        }, {
+            "StartTime": 9187.0,
+            "Position": 155.1861
+        }, {
+            "StartTime": 9250.0,
+            "Position": 179.600418
+        }, {
+            "StartTime": 9312.0,
+            "Position": 212.293015
+        }, {
+            "StartTime": 9375.0,
+            "Position": 197.2076
+        }, {
+            "StartTime": 9437.0,
+            "Position": 243.438324
+        }, {
+            "StartTime": 9500.0,
+            "Position": 237.2304
+        }, {
+            "StartTime": 9562.0,
+            "Position": 241.253983
+        }, {
+            "StartTime": 9625.0,
+            "Position": 258.950623
+        }, {
+            "StartTime": 9687.0,
+            "Position": 253.3786
+        }, {
+            "StartTime": 9750.0,
+            "Position": 270.8865
+        }, {
+            "StartTime": 9812.0,
+            "Position": 244.38974
+        }, {
+            "StartTime": 9875.0,
+            "Position": 242.701874
+        }, {
+            "StartTime": 9937.0,
+            "Position": 256.2331
+        }, {
+            "StartTime": 10000.0,
+            "Position": 270.339874
+        }, {
+            "StartTime": 10062.0,
+            "Position": 275.9349
+        }, {
+            "StartTime": 10125.0,
+            "Position": 297.2969
+        }, {
+            "StartTime": 10187.0,
+            "Position": 307.834137
+        }, {
+            "StartTime": 10250.0,
+            "Position": 321.6449
+        }, {
+            "StartTime": 10312.0,
+            "Position": 357.746338
+        }, {
+            "StartTime": 10375.0,
+            "Position": 358.21875
+        }, {
+            "StartTime": 10437.0,
+            "Position": 394.943
+        }, {
+            "StartTime": 10500.0,
+            "Position": 401.0588
+        }, {
+            "StartTime": 10558.0,
+            "Position": 418.21347
+        }, {
+            "StartTime": 10616.0,
+            "Position": 424.6034
+        }, {
+            "StartTime": 10674.0,
+            "Position": 455.835754
+        }, {
+            "StartTime": 10732.0,
+            "Position": 477.5042
+        }, {
+            "StartTime": 10790.0,
+            "Position": 476.290955
+        }, {
+            "StartTime": 10848.0,
+            "Position": 470.943237
+        }, {
+            "StartTime": 10906.0,
+            "Position": 503.3372
+        }, {
+            "StartTime": 10999.0,
+            "Position": 508.166229
+        }]
+    }, {
+        "StartTime": 11500.0,
+        "Objects": [{
+            "StartTime": 11500.0,
+            "Position": 321.0
+        }, {
+            "StartTime": 11562.0,
+            "Position": 17.0
+        }, {
+            "StartTime": 11625.0,
+            "Position": 173.0
+        }, {
+            "StartTime": 11687.0,
+            "Position": 170.0
+        }, {
+            "StartTime": 11750.0,
+            "Position": 447.0
+        }, {
+            "StartTime": 11812.0,
+            "Position": 218.0
+        }, {
+            "StartTime": 11875.0,
+            "Position": 394.0
+        }, {
+            "StartTime": 11937.0,
+            "Position": 46.0
+        }, {
+            "StartTime": 12000.0,
+            "Position": 480.0
+        }]
+    }, {
+        "StartTime": 12500.0,
+        "Objects": [{
+            "StartTime": 12500.0,
+            "Position": 512.0
+        }, {
+            "StartTime": 12562.0,
+            "Position": 491.3132
+        }, {
+            "StartTime": 12625.0,
+            "Position": 484.3089
+        }, {
+            "StartTime": 12687.0,
+            "Position": 454.6221
+        }, {
+            "StartTime": 12750.0,
+            "Position": 433.617767
+        }, {
+            "StartTime": 12812.0,
+            "Position": 399.930969
+        }, {
+            "StartTime": 12875.0,
+            "Position": 395.926666
+        }, {
+            "StartTime": 12937.0,
+            "Position": 361.239868
+        }, {
+            "StartTime": 13000.0,
+            "Position": 353.235535
+        }, {
+            "StartTime": 13062.0,
+            "Position": 314.548767
+        }, {
+            "StartTime": 13125.0,
+            "Position": 315.544434
+        }, {
+            "StartTime": 13187.0,
+            "Position": 288.857635
+        }, {
+            "StartTime": 13250.0,
+            "Position": 254.853333
+        }, {
+            "StartTime": 13312.0,
+            "Position": 239.166534
+        }, {
+            "StartTime": 13375.0,
+            "Position": 240.1622
+        }, {
+            "StartTime": 13437.0,
+            "Position": 212.4754
+        }, {
+            "StartTime": 13500.0,
+            "Position": 194.471069
+        }, {
+            "StartTime": 13562.0,
+            "Position": 161.784271
+        }, {
+            "StartTime": 13625.0,
+            "Position": 145.779968
+        }, {
+            "StartTime": 13687.0,
+            "Position": 129.09314
+        }, {
+            "StartTime": 13750.0,
+            "Position": 104.088837
+        }, {
+            "StartTime": 13812.0,
+            "Position": 95.40204
+        }, {
+            "StartTime": 13875.0,
+            "Position": 61.3977356
+        }, {
+            "StartTime": 13937.0,
+            "Position": 56.710907
+        }, {
+            "StartTime": 14000.0,
+            "Position": 35.7066345
+        }, {
+            "StartTime": 14062.0,
+            "Position": 5.019806
+        }, {
+            "StartTime": 14125.0,
+            "Position": 0.0
+        }, {
+            "StartTime": 14187.0,
+            "Position": 39.7696266
+        }, {
+            "StartTime": 14250.0,
+            "Position": 23.0119171
+        }, {
+            "StartTime": 14312.0,
+            "Position": 75.94882
+        }, {
+            "StartTime": 14375.0,
+            "Position": 98.19112
+        }, {
+            "StartTime": 14437.0,
+            "Position": 82.12803
+        }, {
+            "StartTime": 14500.0,
+            "Position": 118.370323
+        }, {
+            "StartTime": 14562.0,
+            "Position": 149.307236
+        }, {
+            "StartTime": 14625.0,
+            "Position": 168.549515
+        }, {
+            "StartTime": 14687.0,
+            "Position": 190.486435
+        }, {
+            "StartTime": 14750.0,
+            "Position": 186.728714
+        }, {
+            "StartTime": 14812.0,
+            "Position": 199.665634
+        }, {
+            "StartTime": 14875.0,
+            "Position": 228.907928
+        }, {
+            "StartTime": 14937.0,
+            "Position": 264.844849
+        }, {
+            "StartTime": 15000.0,
+            "Position": 271.087128
+        }, {
+            "StartTime": 15062.0,
+            "Position": 290.024017
+        }, {
+            "StartTime": 15125.0,
+            "Position": 302.266327
+        }, {
+            "StartTime": 15187.0,
+            "Position": 344.203247
+        }, {
+            "StartTime": 15250.0,
+            "Position": 356.445526
+        }, {
+            "StartTime": 15312.0,
+            "Position": 359.382446
+        }, {
+            "StartTime": 15375.0,
+            "Position": 401.624725
+        }, {
+            "StartTime": 15437.0,
+            "Position": 388.561646
+        }, {
+            "StartTime": 15500.0,
+            "Position": 423.803925
+        }, {
+            "StartTime": 15562.0,
+            "Position": 425.740845
+        }, {
+            "StartTime": 15625.0,
+            "Position": 449.983124
+        }, {
+            "StartTime": 15687.0,
+            "Position": 468.920044
+        }, {
+            "StartTime": 15750.0,
+            "Position": 492.162323
+        }, {
+            "StartTime": 15812.0,
+            "Position": 506.784332
+        }, {
+            "StartTime": 15875.0,
+            "Position": 474.226227
+        }, {
+            "StartTime": 15937.0,
+            "Position": 482.978638
+        }, {
+            "StartTime": 16000.0,
+            "Position": 446.420532
+        }, {
+            "StartTime": 16058.0,
+            "Position": 418.4146
+        }, {
+            "StartTime": 16116.0,
+            "Position": 425.408844
+        }, {
+            "StartTime": 16174.0,
+            "Position": 383.402924
+        }, {
+            "StartTime": 16232.0,
+            "Position": 363.397156
+        }, {
+            "StartTime": 16290.0,
+            "Position": 343.391235
+        }, {
+            "StartTime": 16348.0,
+            "Position": 328.385468
+        }, {
+            "StartTime": 16406.0,
+            "Position": 322.3797
+        }, {
+            "StartTime": 16500.0,
+            "Position": 291.1977
+        }]
+    }, {
+        "StartTime": 17000.0,
+        "Objects": [{
+            "StartTime": 17000.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 17062.0,
+            "Position": 228.16
+        }, {
+            "StartTime": 17125.0,
+            "Position": 234.0
+        }, {
+            "StartTime": 17187.0,
+            "Position": 202.16
+        }, {
+            "StartTime": 17250.0,
+            "Position": 176.0
+        }, {
+            "StartTime": 17312.0,
+            "Position": 210.84
+        }, {
+            "StartTime": 17375.0,
+            "Position": 221.0
+        }, {
+            "StartTime": 17437.0,
+            "Position": 219.84
+        }, {
+            "StartTime": 17500.0,
+            "Position": 256.0
+        }, {
+            "StartTime": 17562.0,
+            "Position": 219.16
+        }, {
+            "StartTime": 17625.0,
+            "Position": 228.0
+        }, {
+            "StartTime": 17687.0,
+            "Position": 203.16
+        }, {
+            "StartTime": 17750.0,
+            "Position": 176.0
+        }, {
+            "StartTime": 17803.0,
+            "Position": 174.959991
+        }, {
+            "StartTime": 17857.0,
+            "Position": 214.23999
+        }, {
+            "StartTime": 17910.0,
+            "Position": 228.200012
+        }, {
+            "StartTime": 18000.0,
+            "Position": 256.0
+        }]
+    }, {
+        "StartTime": 18500.0,
+        "Objects": [{
+            "StartTime": 18500.0,
+            "Position": 362.0
+        }, {
+            "StartTime": 18559.0,
+            "Position": 249.0
+        }, {
+            "StartTime": 18618.0,
+            "Position": 357.0
+        }, {
+            "StartTime": 18678.0,
+            "Position": 167.0
+        }, {
+            "StartTime": 18737.0,
+            "Position": 477.0
+        }, {
+            "StartTime": 18796.0,
+            "Position": 411.0
+        }, {
+            "StartTime": 18856.0,
+            "Position": 254.0
+        }, {
+            "StartTime": 18915.0,
+            "Position": 308.0
+        }, {
+            "StartTime": 18975.0,
+            "Position": 399.0
+        }, {
+            "StartTime": 19034.0,
+            "Position": 176.0
+        }, {
+            "StartTime": 19093.0,
+            "Position": 14.0
+        }, {
+            "StartTime": 19153.0,
+            "Position": 258.0
+        }, {
+            "StartTime": 19212.0,
+            "Position": 221.0
+        }, {
+            "StartTime": 19271.0,
+            "Position": 481.0
+        }, {
+            "StartTime": 19331.0,
+            "Position": 92.0
+        }, {
+            "StartTime": 19390.0,
+            "Position": 211.0
+        }, {
+            "StartTime": 19450.0,
+            "Position": 135.0
+        }]
+    }, {
+        "StartTime": 19875.0,
+        "Objects": [{
+            "StartTime": 19875.0,
+            "Position": 216.0
+        }, {
+            "StartTime": 19937.0,
+            "Position": 215.307053
+        }, {
+            "StartTime": 20000.0,
+            "Position": 236.036865
+        }, {
+            "StartTime": 20062.0,
+            "Position": 236.312088
+        }, {
+            "StartTime": 20125.0,
+            "Position": 235.838928
+        }, {
+            "StartTime": 20187.0,
+            "Position": 269.9743
+        }, {
+            "StartTime": 20250.0,
+            "Position": 285.999146
+        }, {
+            "StartTime": 20312.0,
+            "Position": 283.669067
+        }, {
+            "StartTime": 20375.0,
+            "Position": 317.446747
+        }, {
+            "StartTime": 20437.0,
+            "Position": 330.750275
+        }, {
+            "StartTime": 20500.0,
+            "Position": 344.0156
+        }, {
+            "StartTime": 20562.0,
+            "Position": 318.472168
+        }, {
+            "StartTime": 20625.0,
+            "Position": 309.165466
+        }, {
+            "StartTime": 20687.0,
+            "Position": 317.044617
+        }, {
+            "StartTime": 20750.0,
+            "Position": 280.457367
+        }, {
+            "StartTime": 20812.0,
+            "Position": 272.220581
+        }, {
+            "StartTime": 20875.0,
+            "Position": 270.3294
+        }, {
+            "StartTime": 20937.0,
+            "Position": 262.57605
+        }, {
+            "StartTime": 21000.0,
+            "Position": 244.803329
+        }, {
+            "StartTime": 21062.0,
+            "Position": 215.958359
+        }, {
+            "StartTime": 21125.0,
+            "Position": 177.79332
+        }, {
+            "StartTime": 21187.0,
+            "Position": 190.948349
+        }, {
+            "StartTime": 21250.0,
+            "Position": 158.78334
+        }, {
+            "StartTime": 21312.0,
+            "Position": 136.93837
+        }, {
+            "StartTime": 21375.0,
+            "Position": 119.121056
+        }, {
+            "StartTime": 21437.0,
+            "Position": 132.387573
+        }, {
+            "StartTime": 21500.0,
+            "Position": 124.503014
+        }, {
+            "StartTime": 21562.0,
+            "Position": 118.749374
+        }, {
+            "StartTime": 21625.0,
+            "Position": 123.165535
+        }, {
+            "StartTime": 21687.0,
+            "Position": 96.02999
+        }, {
+            "StartTime": 21750.0,
+            "Position": 118.547928
+        }, {
+            "StartTime": 21812.0,
+            "Position": 128.856232
+        }, {
+            "StartTime": 21875.0,
+            "Position": 124.28746
+        }, {
+            "StartTime": 21937.0,
+            "Position": 150.754929
+        }, {
+            "StartTime": 22000.0,
+            "Position": 149.528732
+        }, {
+            "StartTime": 22062.0,
+            "Position": 145.1691
+        }, {
+            "StartTime": 22125.0,
+            "Position": 182.802155
+        }, {
+            "StartTime": 22187.0,
+            "Position": 178.6452
+        }, {
+            "StartTime": 22250.0,
+            "Position": 213.892181
+        }, {
+            "StartTime": 22312.0,
+            "Position": 218.713028
+        }, {
+            "StartTime": 22375.0,
+            "Position": 240.4715
+        }, {
+            "StartTime": 22437.0,
+            "Position": 239.371887
+        }, {
+            "StartTime": 22500.0,
+            "Position": 261.907257
+        }, {
+            "StartTime": 22562.0,
+            "Position": 314.353119
+        }, {
+            "StartTime": 22625.0,
+            "Position": 299.273376
+        }, {
+            "StartTime": 22687.0,
+            "Position": 356.98288
+        }, {
+            "StartTime": 22750.0,
+            "Position": 339.078552
+        }, {
+            "StartTime": 22812.0,
+            "Position": 377.8958
+        }, {
+            "StartTime": 22875.0,
+            "Position": 398.054047
+        }, {
+            "StartTime": 22937.0,
+            "Position": 398.739441
+        }, {
+            "StartTime": 23000.0,
+            "Position": 407.178467
+        }, {
+            "StartTime": 23062.0,
+            "Position": 444.8687
+        }, {
+            "StartTime": 23125.0,
+            "Position": 417.069977
+        }, {
+            "StartTime": 23187.0,
+            "Position": 454.688477
+        }, {
+            "StartTime": 23250.0,
+            "Position": 428.9612
+        }, {
+            "StartTime": 23312.0,
+            "Position": 441.92807
+        }, {
+            "StartTime": 23375.0,
+            "Position": 439.749878
+        }, {
+            "StartTime": 23433.0,
+            "Position": 455.644684
+        }, {
+            "StartTime": 23491.0,
+            "Position": 440.7359
+        }, {
+            "StartTime": 23549.0,
+            "Position": 430.0944
+        }, {
+            "StartTime": 23607.0,
+            "Position": 420.796173
+        }, {
+            "StartTime": 23665.0,
+            "Position": 435.897461
+        }, {
+            "StartTime": 23723.0,
+            "Position": 418.462555
+        }, {
+            "StartTime": 23781.0,
+            "Position": 405.53775
+        }, {
+            "StartTime": 23874.0,
+            "Position": 408.720825
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs
new file mode 100644
index 0000000000..826c900140
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs
@@ -0,0 +1,67 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+    public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    {
+        protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
+
+        [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2149")]
+        public new void Test(string name)
+        {
+            base.Test(name);
+        }
+
+        protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
+        {
+            if (hitObject is JuiceStream stream)
+            {
+                foreach (var nested in stream.NestedHitObjects)
+                {
+                    yield return new ConvertValue
+                    {
+                        StartTime = nested.StartTime,
+                        Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
+                    };
+                }
+            }
+            else
+            {
+                yield return new ConvertValue
+                {
+                    StartTime = hitObject.StartTime,
+                    Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
+                };
+            }
+        }
+
+        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+    }
+
+    public struct ConvertValue : IEquatable<ConvertValue>
+    {
+        /// <summary>
+        /// A sane value to account for osu!stable using ints everwhere.
+        /// </summary>
+        private const float conversion_lenience = 2;
+
+        public double StartTime;
+        public float Position;
+
+        public bool Equals(ConvertValue other)
+            => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+               && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
+    }
+}
diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index 894fdc9b45..4e2cdd24c3 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -95,6 +95,7 @@
     <Compile Include="Objects\Fruit.cs" />
     <Compile Include="Objects\TinyDroplet.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Tests\CatchBeatmapConversionTest.cs" />
     <Compile Include="Tests\TestCaseBananaShower.cs" />
     <Compile Include="Tests\TestCaseCatcherArea.cs" />
     <Compile Include="Tests\TestCaseCatchStacker.cs" />
@@ -128,6 +129,10 @@
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic-expected-conversion.json" />
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic.osu" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..d593b2b052
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,103 @@
+{
+    "Mappings": [{
+            "StartTime": 500,
+            "Objects": [{
+                    "StartTime": 500,
+                    "EndTime": 2500,
+                    "Column": 0
+                },
+                {
+                    "StartTime": 1500,
+                    "EndTime": 2500,
+                    "Column": 1
+                }
+            ]
+        },
+        {
+            "StartTime": 3000,
+            "Objects": [{
+                "StartTime": 3000,
+                "EndTime": 4000,
+                "Column": 2
+            }]
+        },
+        {
+            "StartTime": 4500,
+            "Objects": [{
+                "StartTime": 4500,
+                "EndTime": 5500,
+                "Column": 4
+            }]
+        },
+        {
+            "StartTime": 6000,
+            "Objects": [{
+                "StartTime": 6000,
+                "EndTime": 6500,
+                "Column": 2
+            }]
+        },
+        {
+            "StartTime": 7000,
+            "Objects": [{
+                "StartTime": 7000,
+                "EndTime": 8000,
+                "Column": 2
+            }]
+        },
+        {
+            "StartTime": 8500,
+            "Objects": [{
+                "StartTime": 8500,
+                "EndTime": 11000,
+                "Column": 0
+            }]
+        },
+        {
+            "StartTime": 11500,
+            "Objects": [{
+                "StartTime": 11500,
+                "EndTime": 12000,
+                "Column": 1
+            }]
+        },
+        {
+            "StartTime": 12500,
+            "Objects": [{
+                "StartTime": 12500,
+                "EndTime": 16500,
+                "Column": 4
+            }]
+        },
+        {
+            "StartTime": 17000,
+            "Objects": [{
+                "StartTime": 17000,
+                "EndTime": 18000,
+                "Column": 2
+            }]
+        },
+        {
+            "StartTime": 18500,
+            "Objects": [{
+                "StartTime": 18500,
+                "EndTime": 19450,
+                "Column": 0
+            }]
+        },
+        {
+            "StartTime": 19875,
+            "Objects": [{
+                    "StartTime": 19875,
+                    "EndTime": 23875,
+                    "Column": 1
+                },
+                {
+                    "StartTime": 19875,
+                    "EndTime": 23875,
+                    "Column": 0
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs
new file mode 100644
index 0000000000..2095addc72
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs
@@ -0,0 +1,60 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+    public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    {
+        protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
+
+        private bool isForCurrentRuleset;
+
+        [NonParallelizable]
+        [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2150")]
+        public void Test(string name, bool isForCurrentRuleset)
+        {
+            this.isForCurrentRuleset = isForCurrentRuleset;
+            base.Test(name);
+        }
+
+        protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
+        {
+            yield return new ConvertValue
+            {
+                StartTime = hitObject.StartTime,
+                EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+                Column = ((ManiaHitObject)hitObject).Column
+            };
+        }
+
+        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
+    }
+
+    public struct ConvertValue : IEquatable<ConvertValue>
+    {
+        /// <summary>
+        /// A sane value to account for osu!stable using ints everwhere.
+        /// </summary>
+        private const float conversion_lenience = 2;
+
+        public double StartTime;
+        public double EndTime;
+        public int Column;
+
+        public bool Equals(ConvertValue other)
+            => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+               && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+               && Column == other.Column;
+    }
+}
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index a2e21e2053..a09b3e93a7 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -126,6 +126,7 @@
     <Compile Include="Objects\Note.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ManiaInputManager.cs" />
+    <Compile Include="Tests\ManiaBeatmapConversionTest.cs" />
     <Compile Include="Tests\TestCaseAutoGeneration.cs" />
     <Compile Include="Tests\TestCaseManiaHitObjects.cs" />
     <Compile Include="Tests\TestCaseManiaPlayfield.cs" />
@@ -159,6 +160,10 @@
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic-expected-conversion.json" />
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic.osu" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..b82fddbe79
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,124 @@
+{
+    "Mappings": [{
+            "StartTime": 500,
+            "Objects": [{
+                "StartTime": 500,
+                "EndTime": 2500,
+                "StartX": 96,
+                "StartY": 192,
+                "EndX": 96,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 3000,
+            "Objects": [{
+                "StartTime": 3000,
+                "EndTime": 4000,
+                "StartX": 256,
+                "StartY": 192,
+                "EndX": 256,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 4500,
+            "Objects": [{
+                "StartTime": 4500,
+                "EndTime": 5500,
+                "StartX": 256,
+                "StartY": 192,
+                "EndX": 256,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 6000,
+            "Objects": [{
+                "StartTime": 6000,
+                "EndTime": 6500,
+                "StartX": 256,
+                "StartY": 192,
+                "EndX": 256,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 7000,
+            "Objects": [{
+                "StartTime": 7000,
+                "EndTime": 8000,
+                "StartX": 256,
+                "StartY": 128,
+                "EndX": 256,
+                "EndY": 128
+            }]
+        },
+        {
+            "StartTime": 8500,
+            "Objects": [{
+                "StartTime": 8500,
+                "EndTime": 10999,
+                "StartX": 32,
+                "StartY": 192,
+                "EndX": 508.166229,
+                "EndY": 153.299271
+            }]
+        },
+        {
+            "StartTime": 11500,
+            "Objects": [{
+                "StartTime": 11500,
+                "EndTime": 12000,
+                "StartX": 256,
+                "StartY": 192,
+                "EndX": 256,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 12500,
+            "Objects": [{
+                "StartTime": 12500,
+                "EndTime": 16500,
+                "StartX": 512,
+                "StartY": 320,
+                "EndX": 291.1977,
+                "EndY": 40.799427
+            }]
+        },
+        {
+            "StartTime": 17000,
+            "Objects": [{
+                "StartTime": 17000,
+                "EndTime": 18000,
+                "StartX": 256,
+                "StartY": 256,
+                "EndX": 256,
+                "EndY": 256
+            }]
+        },
+        {
+            "StartTime": 18500,
+            "Objects": [{
+                "StartTime": 18500,
+                "EndTime": 19450,
+                "StartX": 256,
+                "StartY": 192,
+                "EndX": 256,
+                "EndY": 192
+            }]
+        },
+        {
+            "StartTime": 19875,
+            "Objects": [{
+                "StartTime": 19875,
+                "EndTime": 23874,
+                "StartX": 216,
+                "StartY": 231,
+                "EndX": 408.720825,
+                "EndY": 339.810455
+            }]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs
new file mode 100644
index 0000000000..a98f002a4b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs
@@ -0,0 +1,69 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Beatmaps;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+    public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    {
+        protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
+
+        [TestCase("basic")]
+        public new void Test(string name)
+        {
+            base.Test(name);
+        }
+
+        protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
+        {
+            var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192);
+            var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
+
+            yield return new ConvertValue
+            {
+                StartTime = hitObject.StartTime,
+                EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+                StartX = startPosition.X,
+                StartY = startPosition.Y,
+                EndX = endPosition.X,
+                EndY = endPosition.Y
+            };
+        }
+
+        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter();
+    }
+
+    public struct ConvertValue : IEquatable<ConvertValue>
+    {
+        /// <summary>
+        /// A sane value to account for osu!stable using ints everwhere.
+        /// </summary>
+        private const double conversion_lenience = 2;
+
+        public double StartTime;
+        public double EndTime;
+        public float StartX;
+        public float StartY;
+        public float EndX;
+        public float EndY;
+
+        public bool Equals(ConvertValue other)
+            => Precision.AlmostEquals(StartTime, other.StartTime)
+               && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+               && Precision.AlmostEquals(StartX, other.StartX)
+               && Precision.AlmostEquals(StartY, other.StartY, conversion_lenience)
+               && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
+               && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
+    }
+}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 1407c05425..01dda307bc 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -127,6 +127,7 @@
     <Compile Include="OsuDifficulty\Utils\History.cs" />
     <Compile Include="OsuInputManager.cs" />
     <Compile Include="Replays\OsuReplayInputHandler.cs" />
+    <Compile Include="Tests\OsuBeatmapConversionTest.cs" />
     <Compile Include="Tests\TestCaseHitCircle.cs" />
     <Compile Include="Tests\TestCaseHitCircleHidden.cs" />
     <Compile Include="Tests\TestCasePerformancePoints.cs" />
@@ -172,6 +173,10 @@
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic-expected-conversion.json" />
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic.osu" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..5c9310fec7
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,209 @@
+{
+    "Mappings": [{
+            "StartTime": 500,
+            "Objects": [{
+                "StartTime": 500,
+                "EndTime": 2499,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": true,
+                "IsSwell": false,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 3000,
+            "Objects": [{
+                "StartTime": 3000,
+                "EndTime": 4000,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": false,
+                "IsSwell": true,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 4500,
+            "Objects": [{
+                "StartTime": 4500,
+                "EndTime": 5500,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": false,
+                "IsSwell": true,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 6000,
+            "Objects": [{
+                "StartTime": 6000,
+                "EndTime": 6500,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": false,
+                "IsSwell": true,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 7000,
+            "Objects": [{
+                    "StartTime": 7000,
+                    "EndTime": 7000,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 7249,
+                    "EndTime": 7249,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 7499,
+                    "EndTime": 7499,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 7749,
+                    "EndTime": 7749,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 7999,
+                    "EndTime": 7999,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                }
+            ]
+        },
+        {
+            "StartTime": 8500,
+            "Objects": [{
+                "StartTime": 8500,
+                "EndTime": 10999,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": true,
+                "IsSwell": false,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 11500,
+            "Objects": [{
+                "StartTime": 11500,
+                "EndTime": 12000,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": false,
+                "IsSwell": true,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 12500,
+            "Objects": [{
+                "StartTime": 12500,
+                "EndTime": 16499,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": true,
+                "IsSwell": false,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 17000,
+            "Objects": [{
+                    "StartTime": 17000,
+                    "EndTime": 17000,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 17249,
+                    "EndTime": 17249,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 17499,
+                    "EndTime": 17499,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 17749,
+                    "EndTime": 17749,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                },
+                {
+                    "StartTime": 17999,
+                    "EndTime": 17999,
+                    "IsRim": false,
+                    "IsCentre": true,
+                    "IsDrumRoll": false,
+                    "IsSwell": false,
+                    "IsStrong": false
+                }
+            ]
+        },
+        {
+            "StartTime": 18500,
+            "Objects": [{
+                "StartTime": 18500,
+                "EndTime": 19450,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": false,
+                "IsSwell": true,
+                "IsStrong": false
+            }]
+        },
+        {
+            "StartTime": 19875,
+            "Objects": [{
+                "StartTime": 19875,
+                "EndTime": 23874,
+                "IsRim": false,
+                "IsCentre": false,
+                "IsDrumRoll": true,
+                "IsSwell": false,
+                "IsStrong": false
+            }]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs
new file mode 100644
index 0000000000..64f728a018
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs
@@ -0,0 +1,72 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+    public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    {
+        protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
+
+        private bool isForCurrentRuleset;
+
+        [NonParallelizable]
+        [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
+        public void Test(string name, bool isForCurrentRuleset)
+        {
+            this.isForCurrentRuleset = isForCurrentRuleset;
+            base.Test(name);
+        }
+
+        protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
+        {
+            yield return new ConvertValue
+            {
+                StartTime = hitObject.StartTime,
+                EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+                IsRim = hitObject is RimHit,
+                IsCentre = hitObject is CentreHit,
+                IsDrumRoll = hitObject is DrumRoll,
+                IsSwell = hitObject is Swell,
+                IsStrong = ((TaikoHitObject)hitObject).IsStrong
+            };
+        }
+
+        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
+    }
+
+    public struct ConvertValue : IEquatable<ConvertValue>
+    {
+        /// <summary>
+        /// A sane value to account for osu!stable using ints everwhere.
+        /// </summary>
+        private const float conversion_lenience = 2;
+
+        public double StartTime;
+        public double EndTime;
+        public bool IsRim;
+        public bool IsCentre;
+        public bool IsDrumRoll;
+        public bool IsSwell;
+        public bool IsStrong;
+
+        public bool Equals(ConvertValue other)
+            => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+               && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+               && IsRim == other.IsRim
+               && IsCentre == other.IsCentre
+               && IsDrumRoll == other.IsDrumRoll
+               && IsSwell == other.IsSwell
+               && IsStrong == other.IsStrong;
+    }
+}
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index db2db9fff1..07d27455b8 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -112,6 +112,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Scoring\TaikoScoreProcessor.cs" />
     <Compile Include="TaikoInputManager.cs" />
+    <Compile Include="Tests\TaikoBeatmapConversionTest.cs" />
     <Compile Include="Tests\TestCaseInputDrum.cs" />
     <Compile Include="Tests\TestCasePerformancePoints.cs" />
     <Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
@@ -145,6 +146,10 @@
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic-expected-conversion.json" />
+    <EmbeddedResource Include="Resources\Testing\Beatmaps\basic.osu" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 20de4e9680..711e220b88 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -12,8 +12,16 @@ namespace osu.Game.Beatmaps
     /// Converts a Beatmap for another mode.
     /// </summary>
     /// <typeparam name="T">The type of HitObject stored in the Beatmap.</typeparam>
-    public abstract class BeatmapConverter<T> where T : HitObject
+    public abstract class BeatmapConverter<T> : IBeatmapConverter
+        where T : HitObject
     {
+        private event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
+        event Action<HitObject, IEnumerable<HitObject>> IBeatmapConverter.ObjectConverted
+        {
+            add => ObjectConverted += value;
+            remove => ObjectConverted -= value;
+        }
+
         /// <summary>
         /// Checks if a Beatmap can be converted using this Beatmap Converter.
         /// </summary>
@@ -32,6 +40,8 @@ namespace osu.Game.Beatmaps
             return ConvertBeatmap(new Beatmap(original));
         }
 
+        void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
+
         /// <summary>
         /// Performs the conversion of a Beatmap using this Beatmap Converter.
         /// </summary>
@@ -63,8 +73,11 @@ namespace osu.Game.Beatmaps
                 yield break;
             }
 
+            var converted = ConvertHitObject(original, beatmap).ToList();
+            ObjectConverted?.Invoke(original, converted);
+
             // Convert the hit object
-            foreach (var obj in ConvertHitObject(original, beatmap))
+            foreach (var obj in converted)
             {
                 if (obj == null)
                     continue;
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
new file mode 100644
index 0000000000..ebd900b97e
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Beatmaps
+{
+    public interface IBeatmapConverter
+    {
+        /// <summary>
+        /// Invoked when a <see cref="HitObject"/> has been converted.
+        /// The first argument contains the <see cref="HitObject"/> that was converted.
+        /// The second argument contains the <see cref="HitObject"/>s that were output from the conversion process.
+        /// </summary>
+        event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
+
+        /// <summary>
+        /// Converts a Beatmap using this Beatmap Converter.
+        /// </summary>
+        /// <param name="original">The un-converted Beatmap.</param>
+        void Convert(Beatmap beatmap);
+    }
+}
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
new file mode 100644
index 0000000000..596dbe84ba
--- /dev/null
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -0,0 +1,141 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Newtonsoft.Json;
+using NUnit.Framework;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Tests.Beatmaps
+{
+    [TestFixture]
+    public abstract class BeatmapConversionTest<TConvertValue>
+        where TConvertValue : IEquatable<TConvertValue>
+    {
+        private const string resource_namespace = "Testing.Beatmaps";
+        private const string expected_conversion_suffix = "-expected-conversion";
+
+        protected abstract string ResourceAssembly { get; }
+
+        protected void Test(string name)
+        {
+            var ourResult = convert(name);
+            var expectedResult = read(name);
+
+            Assert.Multiple(() =>
+            {
+                int mappingCounter = 0;
+                while (true)
+                {
+                    if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count)
+                        break;
+                    if (mappingCounter >= ourResult.Mappings.Count)
+                        Assert.Fail($"A conversion did not generate any hitobjects, but should have, for hitobject at time: {expectedResult.Mappings[mappingCounter].StartTime}\n");
+                    else if (mappingCounter >= expectedResult.Mappings.Count)
+                        Assert.Fail($"A conversion generated hitobjects, but should not have, for hitobject at time: {ourResult.Mappings[mappingCounter].StartTime}\n");
+                    else
+                    {
+                        var counter = mappingCounter;
+                        Assert.Multiple(() =>
+                        {
+                            var ourMapping = ourResult.Mappings[counter];
+                            var expectedMapping = expectedResult.Mappings[counter];
+
+                            int objectCounter = 0;
+                            while (true)
+                            {
+                                if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count)
+                                    break;
+                                if (objectCounter >= ourMapping.Objects.Count)
+                                    Assert.Fail($"The conversion did not generate a hitobject, but should have, for hitobject at time: {expectedMapping.StartTime}:\n"
+                                                + $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n");
+                                else if (objectCounter >= expectedMapping.Objects.Count)
+                                    Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
+                                                + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
+                                else if (!EqualityComparer<TConvertValue>.Default.Equals(expectedMapping.Objects[objectCounter], ourMapping.Objects[objectCounter]))
+                                {
+                                    Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}\n"
+                                                + $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
+                                                + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
+                                }
+
+                                objectCounter++;
+                            }
+                        });
+                    }
+
+                    mappingCounter++;
+                }
+            });
+        }
+
+        private ConvertResult convert(string name)
+        {
+            var beatmap = getBeatmap(name);
+
+            var result = new ConvertResult();
+
+            var converter = CreateConverter(beatmap);
+            converter.ObjectConverted += (orig, converted) =>
+            {
+                converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
+
+                var mapping = new ConvertMapping { StartTime = orig.StartTime };
+                foreach (var obj in converted)
+                    mapping.Objects.AddRange(CreateConvertValue(obj));
+                result.Mappings.Add(mapping);
+            };
+
+            converter.Convert(beatmap);
+
+            return result;
+        }
+
+        private ConvertResult read(string name)
+        {
+            using (var resStream = openResource($"{resource_namespace}.{name}{expected_conversion_suffix}.json"))
+            using (var reader = new StreamReader(resStream))
+            {
+                var contents = reader.ReadToEnd();
+                return JsonConvert.DeserializeObject<ConvertResult>(contents);
+            }
+        }
+
+        private Beatmap getBeatmap(string name)
+        {
+            var decoder = new LegacyBeatmapDecoder();
+            using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
+            using (var stream = new StreamReader(resStream))
+                return decoder.DecodeBeatmap(stream);
+        }
+
+        private Stream openResource(string name)
+        {
+            var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
+            return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
+        }
+
+        protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
+        protected abstract IBeatmapConverter CreateConverter(Beatmap beatmap);
+
+        private class ConvertMapping
+        {
+            [JsonProperty]
+            public double StartTime;
+            [JsonProperty]
+            public List<TConvertValue> Objects = new List<TConvertValue>();
+        }
+
+        private class ConvertResult
+        {
+            [JsonProperty]
+            public List<ConvertMapping> Mappings = new List<ConvertMapping>();
+        }
+    }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index d3c4dd9c92..ff365ad93e 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -270,6 +270,7 @@
     <Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" />
     <Compile Include="Beatmaps\Formats\LegacyDecoder.cs" />
     <Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
+    <Compile Include="Beatmaps\IBeatmapConverter.cs" />
     <Compile Include="Configuration\DatabasedSetting.cs" />
     <Compile Include="Configuration\SettingsStore.cs" />
     <Compile Include="Configuration\DatabasedConfigManager.cs" />
@@ -883,6 +884,7 @@
     <Compile Include="Storyboards\StoryboardLayer.cs" />
     <Compile Include="Storyboards\StoryboardSample.cs" />
     <Compile Include="Storyboards\StoryboardSprite.cs" />
+    <Compile Include="Tests\Beatmaps\BeatmapConversionTest.cs" />
     <Compile Include="Tests\Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\CleanRunHeadlessGameHost.cs" />
     <Compile Include="Tests\Platform\TestStorage.cs" />