소스 검색

update travis and appveyor

JasonWang 6 년 전
부모
커밋
1147173b98

+ 17 - 16
.travis.yml

@@ -6,22 +6,23 @@ script:
  - cd ./src
  - dotnet restore
  - dotnet build -c TravisCI
+# stop all test and docker buid for refactor
 # test
- - cd ../tests
- - dotnet test
+# - cd ../tests
+# - dotnet test
 # docker build
- - cd ../src/MineCase.Gateway
- - dotnet publish -c TravisCI -o obj/Docker/publish
- - docker build -t minecase.gateway .
- - cd ../MineCase.Server
- - dotnet publish -c TravisCI -o obj/Docker/publish
- - docker build -t minecase.server .
+# - cd ../src/MineCase.Gateway
+# - dotnet publish -c TravisCI -o obj/Docker/publish
+# - docker build -t minecase.gateway .
+# - cd ../MineCase.Server
+# - dotnet publish -c TravisCI -o obj/Docker/publish
+# - docker build -t minecase.server .
 # docker push
- - docker tag minecase.gateway sunnycase/minecase.gateway:ci-latest
- - docker tag minecase.server sunnycase/minecase.server:ci-latest
- - >
-   if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
-     docker login -u="$DOCKER_USER" -p="$DOCKER_PASS"
-     docker push sunnycase/minecase.gateway
-     docker push sunnycase/minecase.server
-   fi
+# - docker tag minecase.gateway sunnycase/minecase.gateway:ci-latest
+# - docker tag minecase.server sunnycase/minecase.server:ci-latest
+# - >
+#   if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
+#     docker login -u="$DOCKER_USER" -p="$DOCKER_PASS"
+#     docker push sunnycase/minecase.gateway
+#     docker push sunnycase/minecase.server
+#   fi

+ 6 - 5
appveyor.yml

@@ -21,13 +21,14 @@ build:
   project: ./src/
   parallel: false
   verbosity: minimal
+# stop all test and docker buid for refactor
 test_script:
 - ps: >-
-    cd ..\tests
-
-    OpenCover.Console.exe -oldstyle -register:user -target:"C:\Program Files\dotnet\dotnet.exe" -targetargs:"test" -output:".\test_coverage.xml"
-
-    codecov -f "test_coverage.xml"
+#    cd ..\tests
+#
+#    OpenCover.Console.exe -oldstyle -register:user -target:"C:\Program Files\dotnet\dotnet.exe" -targetargs:"test" -output:".\test_coverage.xml"
+#
+#    codecov -f "test_coverage.xml"
 deploy_script:
 - ps: >-
 #    docker tag minecase.gateway sunnycase/minecase.gateway:ci-latest-nanoserver

+ 4 - 0
src/MineCase.Protocol/MineCase.Protocol.csproj

@@ -10,6 +10,10 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
   <ItemGroup>
     <None Remove="stylecop.json" />
   </ItemGroup>

+ 31 - 0
tests/MineCase.Tests.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27004.2005
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MineCase.UnitTest", "UnitTest\MineCase.UnitTest.csproj", "{8EA43298-B73A-4A32-9A43-59D385ADA787}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Appveyor|Any CPU = Appveyor|Any CPU
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+		TravisCI|Any CPU = TravisCI|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Appveyor|Any CPU.ActiveCfg = Appveyor|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Appveyor|Any CPU.Build.0 = Appveyor|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.TravisCI|Any CPU.ActiveCfg = TravisCI|Any CPU
+		{8EA43298-B73A-4A32-9A43-59D385ADA787}.TravisCI|Any CPU.Build.0 = TravisCI|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {F4A5642D-56BF-4E11-AEDF-3D900DE95C69}
+	EndGlobalSection
+EndGlobal

+ 313 - 0
tests/UnitTest/ChatTest.cs

@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Generic;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class ChatTest
+    {
+        /// <summary>
+        /// Tests Chat.ToJObject.
+        /// </summary>
+        [Fact]
+        public void Test1()
+        {
+            var chat = new Chat(new StringComponent
+            {
+                Text = "hello case!",
+                Bold = true,
+                Itatic = false,
+                Color = ColorType.Blue,
+                ClickEvent = new ChatClickEvent(ClickEventType.OpenUrl, @"http://case.orz")
+            });
+
+            var o = chat.ToJObject();
+            var o2 = JObject.Parse(chat.ToString());
+            Assert.Equal("hello case!", o.GetValue("text"));
+            Assert.True(o.GetValue("bold").Value<bool>());
+            Assert.Equal("blue", o.GetValue("color"));
+            Assert.Equal("open_url", (string)o.SelectToken("clickEvent.action"));
+            Assert.Equal(@"http://case.orz", (string)o.SelectToken("clickEvent.value"));
+            Assert.True(JToken.DeepEquals(o, o2));
+        }
+
+        /// <summary>
+        /// Tests Chat.ToString.
+        /// </summary>
+        [Fact]
+        public void Test2()
+        {
+            var stringComponent = new StringComponent
+            {
+                Text = "hello",
+                Bold = true,
+                Itatic = false,
+                Color = ColorType.Green,
+                ClickEvent = new ChatClickEvent
+                {
+                    Action = ClickEventType.ChangePage,
+                    Value = 1
+                }
+            };
+
+            var chat = new Chat(stringComponent);
+            Assert.Equal("{\"bold\":true,\"itatic\":false,\"color\":\"green\",\"clickEvent\":{\"action\":\"change_page\",\"value\":1},\"text\":\"hello\"}", chat.ToString());
+        }
+
+        /// <summary>
+        /// Tests Chat.Parse.
+        /// </summary>
+        [Fact]
+        public void Test3()
+        {
+            const string json = @"{
+                ""text"":""case"",
+                ""bold"":true,
+                ""itatic"":true,
+                ""color"":""red"",
+                ""clickEvent"":{
+                    ""action"":""change_page"",
+                    ""value"":1
+                },
+                ""extra"":[
+                    { ""text"":""foo"",""bold"":false },
+                    { ""text"":""bar"" }
+                ]
+            }";
+
+            var chat = Chat.Parse(json);
+            var jObject = JObject.Parse(json);
+
+            var sc = (StringComponent)chat.Component;
+            Assert.Equal("case", sc.Text);
+            Assert.True(sc.Bold);
+            Assert.True(sc.Itatic);
+            Assert.Equal(ColorType.Red, sc.Color);
+            Assert.Equal(ClickEventType.ChangePage, sc.ClickEvent.Action);
+            Assert.Equal(1, sc.ClickEvent.Value.Value<int>());
+            Assert.False(jObject.SelectToken("extra[0].bold").Value<bool>());
+            Assert.Equal("foo", jObject.SelectToken("extra[0].text"));
+            Assert.Equal("bar", jObject.SelectToken("extra[1].text"));
+        }
+
+        /// <summary>
+        /// More test cases.
+        /// </summary>
+        [Fact]
+        public void Test4()
+        {
+            var chat = new Chat("text")
+            {
+                Component =
+                {
+                    Bold = true,
+                    Itatic = false,
+                    Insertion = "insert",
+                    Color = ColorType.Red,
+                    ClickEvent = new ChatClickEvent(ClickEventType.ChangePage, 1),
+                    HoverEvent = new ChatHoverEvent(HoverEventType.ShowText, "show")
+                }
+            };
+            var keybind = new KeybindComponent
+            {
+                Keybind = KeyBindType.Back,
+                Bold = false,
+                Itatic = true,
+                Color = ColorType.Green
+            };
+            chat.Component.Extra = new List<ChatComponent> { keybind };
+
+            var json = chat.ToJObject();
+            Assert.Null(json.SelectToken("underlined"));
+            Assert.Null(json.SelectToken("strikethrough"));
+            Assert.Null(json.SelectToken("obfuscated"));
+            Assert.True(json.SelectToken("bold").Value<bool>());
+            Assert.False(json.SelectToken("extra[0].bold").Value<bool>());
+            Assert.False(json.SelectToken("itatic").Value<bool>());
+            Assert.True(json.SelectToken("extra[0].itatic").Value<bool>());
+            Assert.Equal("red", json.SelectToken("color"));
+            Assert.Equal("green", json.SelectToken("extra[0].color"));
+            Assert.Equal("key.back", json.SelectToken("extra[0].keybind"));
+            Assert.Equal("insert", json.SelectToken("insertion"));
+            Assert.Equal("change_page", json.SelectToken("clickEvent.action"));
+            Assert.Equal(1, json.SelectToken("clickEvent.value").Value<int>());
+            Assert.Equal("show_text", json.SelectToken("hoverEvent.action"));
+            Assert.Equal("show", json.SelectToken("hoverEvent.value"));
+        }
+
+        /// <summary>
+        /// Tests TranslationComponent.
+        /// </summary>
+        [Fact]
+        public void Test5()
+        {
+            var sc = new StringComponent("text")
+            {
+                Bold = true,
+                ClickEvent = new ChatClickEvent(ClickEventType.RunCommand, "/msg a")
+            };
+            var list = new List<ChatComponent> { sc, new StringComponent("nothing") };
+
+            var chat = new Chat(new TranslationComponent("chat.type.text", list));
+            var jObject = chat.ToJObject();
+            Assert.Equal("chat.type.text", jObject.SelectToken("translate"));
+            Assert.True(jObject.SelectToken("with[0].bold").Value<bool>());
+            Assert.Equal("run_command", jObject.SelectToken("with[0].clickEvent.action"));
+            Assert.Equal("/msg a", jObject.SelectToken("with[0].clickEvent.value"));
+            Assert.Equal("text", jObject.SelectToken("with[0].text"));
+            Assert.Equal("nothing", jObject.SelectToken("with[1].text"));
+
+            string json = @"{
+                ""translate"":""chat.type.text"",
+                ""with"":[
+                    {
+                        ""bold"":true,
+                        ""text"":""text"",
+                        ""clickEvent"":{""action"":""run_command"",""value"":""/msg a""}
+                    },
+                    {
+                        ""text"":""nothing"" 
+                    }
+                ]
+            }";
+            var chat2 = Chat.Parse(json);
+            Assert.True(JToken.DeepEquals(jObject, chat2.ToJObject()));
+        }
+
+        /// <summary>
+        /// Tests KeybindComponent.
+        /// </summary>
+        [Fact]
+        public void Test6()
+        {
+            var keybind = new KeybindComponent(KeyBindType.Attack)
+            {
+                Extra = new List<ChatComponent>
+                {
+                    new StringComponent("text1"),
+                    new StringComponent("text2")
+                }
+            };
+
+            var chat = new Chat(keybind);
+            var j = chat.ToJObject();
+            Assert.Equal("key.attack", j.SelectToken("keybind"));
+            Assert.Equal("text1", j.SelectToken("extra[0].text"));
+            Assert.Equal("text2", j.SelectToken("extra[1].text"));
+
+            const string json = @"{
+                ""keybind"":""key.attack"",
+                ""extra"":[
+                    { ""text"":""text1"" },
+                    { ""text"":""text2"" }
+                ]
+            }";
+            var chat2 = Chat.Parse(json);
+            Assert.True(JToken.DeepEquals(j, chat2.ToJObject()));
+        }
+
+        /// <summary>
+        /// Tests ScoreComponent.
+        /// </summary>
+        [Fact]
+        public void Test7()
+        {
+            var score = new ScoreComponent(new ChatScore("case", "ball", 100))
+            {
+                Extra = new List<ChatComponent>
+                {
+                    new StringComponent("text1"),
+                    new StringComponent("text2")
+                }
+            };
+
+            var chat = new Chat(score);
+            var j = chat.ToJObject();
+            Assert.Equal("case", j.SelectToken("score.name"));
+            Assert.Equal("ball", j.SelectToken("score.objective"));
+            Assert.Equal(100, j.SelectToken("score.value").Value<int>());
+            Assert.Equal("text1", j.SelectToken("extra[0].text"));
+            Assert.Equal("text2", j.SelectToken("extra[1].text"));
+
+            const string json = @"{
+                ""score"":{
+                    ""name"":""case"",
+                    ""objective"":""ball"",
+                    ""value"":100
+                },
+                ""extra"":[
+                    { ""text"":""text1"" },
+                    { ""text"":""text2"" }
+                ]
+            }";
+            var chat2 = Chat.Parse(json);
+            Assert.True(JToken.DeepEquals(j, chat2.ToJObject()));
+        }
+
+        /// <summary>
+        /// Tests SelectorComponent.
+        /// </summary>
+        [Fact]
+        public void Test8()
+        {
+            var chat = new Chat(new SelectorComponent("@p"))
+            {
+                Component =
+                {
+                    Extra = new List<ChatComponent>
+                    {
+                        new StringComponent("text1"),
+                        new StringComponent("text2")
+                    }
+                }
+            };
+
+            var j = chat.ToJObject();
+            Assert.Equal("@p", j.SelectToken("selector"));
+            Assert.Equal("text1", j.SelectToken("extra[0].text"));
+            Assert.Equal("text2", j.SelectToken("extra[1].text"));
+
+            const string json = @"{
+                ""selector"":""@p"",
+                ""extra"":[
+                    { ""text"":""text1"" },
+                    { ""text"":""text2"" }
+                ]
+            }";
+            var chat2 = Chat.Parse(json);
+            Assert.True(JToken.DeepEquals(j, chat2.ToJObject()));
+        }
+
+        /// <summary>
+        /// Tests exceptions.
+        /// </summary>
+        [Fact]
+        public void Test9()
+        {
+            // parse exception
+            const string json = @"{""text"":}";
+            Assert.Throws<JsonException>(() => Chat.Parse(json));
+
+            // no key exception
+            Chat chat = new Chat();  // no compoent
+            Assert.Throws<InvalidOperationException>(() => chat.ToJObject());
+
+            // no key exception
+            var comp = new StringComponent();
+            Chat chat2 = new Chat(comp); // compoent no specified key
+            Assert.Throws<InvalidOperationException>(() => chat2.ToJObject());
+
+            // no key exception
+            Chat chat3 = new Chat(
+                new ScoreComponent(
+                    new ChatScore
+                    {
+                        Name = "case" // no name key or objective key
+                    }));
+            Assert.Throws<InvalidOperationException>(() => chat3.ToJObject());
+        }
+    }
+}

+ 59 - 0
tests/UnitTest/CollisionTest.cs

@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using MineCase.Algorithm;
+using MineCase.Graphics;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class CollisionTest
+    {
+        [Fact]
+        public void TestCollsion()
+        {
+            var shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 2f));
+            var shape2 = new Cuboid(new Point3d(0.9f, 0.9f, 1f), new Size(1f, 1f, 1f));
+            var result = Collision.IsCollided(shape1, shape2);
+            Assert.True(result);
+
+            shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 1f));
+            shape2 = new Cuboid(new Point3d(-0.5f, 0.3f, 0.5f), new Size(3f, 0.1f, 0.1f));
+            result = Collision.IsCollided(shape1, shape2);
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void TestNotCollsion()
+        {
+            var shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 2f));
+            var shape2 = new Cuboid(new Point3d(2f, 2f, 1f), new Size(1f, 1f, 1f));
+            var result = Collision.IsCollided(shape1, shape2);
+            Assert.False(result);
+
+            shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 1f));
+            shape2 = new Cuboid(new Point3d(0f, 0f, 2f), new Size(1f, 1f, 1f));
+            result = Collision.IsCollided(shape1, shape2);
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void TestBoundaryCollsion()
+        {
+            var shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 2f));
+            var shape2 = new Cuboid(new Point3d(1f, 0f, 0f), new Size(1f, 1f, 1f));
+            var result = Collision.IsCollided(shape1, shape2);
+            Assert.True(result);
+
+            shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 1f));
+            shape2 = new Cuboid(new Point3d(0f, 0f, 1f), new Size(1f, 1f, 1f));
+            result = Collision.IsCollided(shape1, shape2);
+            Assert.True(result);
+
+            shape1 = new Cuboid(new Point3d(0f, 0f, 0f), new Size(1f, 1f, 1f));
+            shape2 = new Cuboid(new Point3d(1f, 1f, 1f), new Size(1f, 1f, 1f));
+            result = Collision.IsCollided(shape1, shape2);
+            Assert.True(result);
+        }
+    }
+}

+ 60 - 0
tests/UnitTest/CraftingRecipeTest.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Algorithm;
+using MineCase.Block;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class CraftingRecipeTest
+    {
+        public readonly string RootDir;
+
+        public CraftingRecipeTest()
+        {
+            RootDir = SetRootDir();
+        }
+
+        private static string SetRootDir([CallerFilePath]string fileName = null) =>
+            Path.Combine(Path.GetDirectoryName(fileName), @"../../data");
+
+        [Fact]
+        public async Task TestCraftingRecipeLoader()
+        {
+            var loader = new CraftingRecipeLoader();
+            using (var sr = new StreamReader(File.OpenRead(Path.Combine(RootDir, "crafting.txt"))))
+            {
+                await loader.LoadRecipes(sr);
+            }
+
+            var recipes = loader.Recipes;
+            Assert.Equal(13, recipes.Count);
+        }
+
+        [Fact]
+        public async Task TestCraftingRecipeMatcher()
+        {
+            var loader = new CraftingRecipeLoader();
+            using (var sr = new StreamReader(File.OpenRead(Path.Combine(RootDir, "crafting.txt"))))
+            {
+                await loader.LoadRecipes(sr);
+            }
+
+            var matcher = new CraftingRecipeMatcher(loader.Recipes);
+            var recipe = matcher.FindRecipe(new Slot[,]
+            {
+                { Slot.Empty, Slot.Empty, Slot.Empty },
+                { new Slot { BlockId = (short)BlockStates.Wood().Id, ItemCount = 1 }, Slot.Empty, Slot.Empty },
+                { Slot.Empty, Slot.Empty, Slot.Empty },
+            });
+            Assert.NotNull(recipe);
+            Assert.Equal((short)BlockStates.WoodPlanks().Id, recipe.Result.BlockId);
+            Assert.True(recipe.AfterTake.Cast<Slot>().All(o => o.IsEmpty));
+        }
+    }
+}

+ 61 - 0
tests/UnitTest/FurnaceRecipeTest.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Algorithm;
+using MineCase.Block;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class FurnaceRecipeTest
+    {
+        public readonly string RootDir;
+
+        public FurnaceRecipeTest()
+        {
+            RootDir = SetRootDir();
+        }
+
+        private static string SetRootDir([CallerFilePath]string fileName = null) =>
+            Path.Combine(Path.GetDirectoryName(fileName), @"../../data");
+
+        [Fact]
+        public async Task TestFurnaceRecipeLoader()
+        {
+            var loader = new FurnaceRecipeLoader();
+            using (var sr = new StreamReader(File.OpenRead(Path.Combine(RootDir, "furnace.txt"))))
+            {
+                await loader.LoadRecipes(sr);
+            }
+
+            var recipes = loader.Recipes;
+            Assert.Equal(2, recipes.Count);
+            var fuels = loader.Fuels;
+            Assert.Equal(4, fuels.Count);
+        }
+
+        [Fact]
+        public async Task TestFurnaceRecipeMatcher()
+        {
+            var loader = new FurnaceRecipeLoader();
+            using (var sr = new StreamReader(File.OpenRead(Path.Combine(RootDir, "furnace.txt"))))
+            {
+                await loader.LoadRecipes(sr);
+            }
+
+            var matcher = new FurnaceRecipeMatcher(loader.Recipes, loader.Fuels);
+            var recipe = matcher.FindRecipe(
+                new Slot { BlockId = (short)BlockStates.Wood().Id, ItemCount = 1 });
+            var fuel = matcher.FindFuel(
+                new Slot { BlockId = (short)BlockStates.Wood().Id, ItemCount = 1 });
+
+            Assert.NotNull(recipe);
+            Assert.Equal(ItemStates.Coal(CoalType.Charcoal), new ItemState { Id = (uint)recipe.Output.BlockId, MetaValue = (uint)recipe.Output.ItemDamage });
+            Assert.Equal((short)BlockStates.Wood().Id, fuel.Slot.BlockId);
+        }
+    }
+}

+ 46 - 0
tests/UnitTest/GenLayerTest.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MineCase.Algorithm.World.Layer;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class GenLayerTest
+    {
+        [Fact]
+        public void Test1()
+        {
+            using (StreamWriter sw = new StreamWriter("../../GenLayer.txt"))
+            {
+                GenLayer layer = GenLayer.InitAllLayer(1);
+                int x = -4;
+                int z = -4;
+                int[,] biomeIds = layer.GetInts(16 * x - 16, 16 * z - 16, 48, 48);
+                sw.WriteLine("pre: ");
+                for (int i = 0; i < 48; ++i)
+                {
+                    for (int j = 0; j < 48; ++j)
+                    {
+                        sw.Write(biomeIds[i, j] + " ");
+                    }
+
+                    sw.WriteLine();
+                }
+
+                biomeIds = layer.GetInts(16 * x, 16 * z, 16, 16);
+                sw.WriteLine("suc: ");
+                for (int i = 0; i < 16; ++i)
+                {
+                    for (int j = 0; j < 16; ++j)
+                    {
+                        sw.Write(biomeIds[i, j] + " ");
+                    }
+
+                    sw.WriteLine();
+                }
+            }
+        }
+    }
+}

+ 41 - 0
tests/UnitTest/MineCase.UnitTest.csproj

@@ -0,0 +1,41 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <CodeAnalysisRuleSet>../../build/Analyzers.ruleset</CodeAnalysisRuleSet>
+    <Configurations>Debug;Release;Appveyor;TravisCI</Configurations>
+    <DebugType>full</DebugType>
+    <DebugSymbols>true</DebugSymbols>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0006" />
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <AdditionalFiles Include="..\..\build\stylecop.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\MineCase.Algorithm\MineCase.Algorithm.csproj" />
+    <ProjectReference Include="..\..\src\MineCase.Engine\MineCase.Server.Engine.csproj" />
+    <ProjectReference Include="..\..\src\MineCase.Nbt\MineCase.Nbt.csproj" />
+    <ProjectReference Include="..\..\src\MineCase.Protocol\MineCase.Protocol.csproj" />
+    <ProjectReference Include="..\..\src\MineCase.Serialization\MineCase.Serialization.csproj" />
+    <ProjectReference Include="..\..\src\MineCase.Server.Interfaces\MineCase.Server.Interfaces.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\..\src\MineCase.Protocol\Serialization\BinaryWriterExtensions.cs" Link="BinaryWriterExtensions.cs" />
+  </ItemGroup>
+
+</Project>

+ 123 - 0
tests/UnitTest/NbtTest.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MineCase.Nbt;
+using MineCase.Nbt.Tags;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class NbtTest
+    {
+        private class OutputVisitor : INbtTagVisitor
+        {
+            private readonly TextWriter _tw;
+            private readonly List<bool> _lastVisitedList = new List<bool>();
+            private int _curDepth;
+
+            public OutputVisitor(TextWriter tw)
+            {
+                _tw = tw;
+            }
+
+            public void StartChild()
+            {
+                _tw.Write($"{Enumerable.Repeat('\t', _curDepth).Aggregate("", (s, c) => string.Concat(s, c))}");
+                _tw.WriteLine(_lastVisitedList.Last() ? '[' : '{');
+                ++_curDepth;
+            }
+
+            public void EndChild()
+            {
+                --_curDepth;
+                _tw.Write($"{Enumerable.Repeat('\t', _curDepth).Aggregate("", (s, c) => string.Concat(s, c))}");
+                _tw.WriteLine(_lastVisitedList.Last() ? ']' : '}');
+                _lastVisitedList.RemoveAt(_lastVisitedList.Count - 1);
+            }
+
+            public void VisitTag(NbtTag tag)
+            {
+                if (tag.TagType == NbtTagType.List || tag.TagType == NbtTagType.Compound)
+                {
+                    _lastVisitedList.Add(tag.TagType == NbtTagType.List);
+                }
+
+                _tw.Write($"{Enumerable.Repeat('\t', _curDepth).Aggregate("", (s, c) => string.Concat(s, c))}{tag.Name ?? "(null)"} : ");
+                switch (tag.TagType)
+                {
+                    case NbtTagType.End:
+                        break;
+                    case NbtTagType.Byte:
+                        _tw.WriteLine(((NbtByte)tag).Value);
+                        break;
+                    case NbtTagType.Short:
+                        _tw.WriteLine(((NbtShort)tag).Value);
+                        break;
+                    case NbtTagType.Int:
+                        _tw.WriteLine(((NbtInt)tag).Value);
+                        break;
+                    case NbtTagType.Long:
+                        _tw.WriteLine(((NbtLong)tag).Value);
+                        break;
+                    case NbtTagType.Float:
+                        _tw.WriteLine(((NbtFloat)tag).Value);
+                        break;
+                    case NbtTagType.Double:
+                        _tw.WriteLine(((NbtDouble)tag).Value);
+                        break;
+                    case NbtTagType.ByteArray:
+                        _tw.WriteLine("(ByteArray)");
+                        break;
+                    case NbtTagType.String:
+                        _tw.WriteLine(((NbtString)tag).Value);
+                        break;
+                    case NbtTagType.List:
+                        _tw.WriteLine();
+                        break;
+                    case NbtTagType.Compound:
+                        _tw.WriteLine();
+                        break;
+                    case NbtTagType.IntArray:
+                        _tw.WriteLine("(IntArray)");
+                        break;
+                }
+            }
+        }
+
+        [Fact]
+        public void Test1()
+        {
+            var nbtFile = new NbtFile();
+            nbtFile.RootTag.Add(new NbtInt(1, "23333"));
+            nbtFile.RootTag.Add(new NbtCompound("test"));
+            var testCompound = nbtFile.RootTag["test"] as NbtCompound;
+            Assert.NotNull(testCompound);
+            var testList = new NbtList(NbtTagType.Int, "testList");
+            testCompound.Add(testList);
+            testList.Add(new NbtInt(2));
+            testList.Add(new NbtInt(4));
+            testCompound.Add(new NbtLong(0x000000FFFFFFFFFF, "testLong"));
+
+            using (var sw = new StringWriter())
+            {
+                nbtFile.RootTag.Accept(new OutputVisitor(sw));
+                var str = sw.ToString();
+                Console.WriteLine(str);
+            }
+
+            var stream = new FileStream("test.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite);
+            stream.SetLength(0);
+            nbtFile.WriteTo(stream);
+
+            stream.Seek(0, SeekOrigin.Begin);
+            var nbtFile2 = new NbtFile(stream);
+
+            using (var sw = new StringWriter())
+            {
+                nbtFile2.RootTag.Accept(new OutputVisitor(sw));
+                Console.WriteLine(sw.ToString());
+            }
+        }
+    }
+}

+ 103 - 0
tests/UnitTest/NoiseTest.cs

@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Numerics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+using MineCase.Algorithm.Noise;
+using SixLabors.ImageSharp;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class NoiseTest
+    {
+        public readonly string RootDir;
+
+        public NoiseTest()
+        {
+            RootDir = SetRootDir();
+        }
+
+        private static string SetRootDir([CallerFilePath]string fileName = null) =>
+            Path.Combine(Path.GetDirectoryName(fileName), "bin");
+
+        [Fact]
+        public void TestPerlinNoise3D()
+        {
+            const int xExtent = 100;
+            const int yExtent = 100;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "PerlinNoise3D.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                var noise = new PerlinNoise(100);
+                var noiseValue = new float[xExtent, yExtent, 1];
+                noise.Noise(noiseValue, Vector3.Zero, new Vector3(0.1f, 0.1f, 0));
+                for (int x = 0; x < xExtent; x++)
+                {
+                    for (int y = 0; y < yExtent; y++)
+                    {
+                        var color = (byte)(noiseValue[x, y, 0] * 255);
+                        image[x, y] = new SixLabors.ImageSharp.PixelFormats.Rgb24(color, color, color);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestOctavedPerlinNoise3D()
+        {
+            const int xExtent = 100;
+            const int yExtent = 100;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "OctavedPerlinNoise3D.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                var noise = new OctavedNoise<PerlinNoise>(new PerlinNoise(100), 8, 0.25f);
+                var noiseValue = new float[xExtent, yExtent, 1];
+                noise.Noise(noiseValue, new Vector3(-10, 10, -10), new Vector3(0.1f, 0.1f, 0));
+                for (int x = 0; x < xExtent; x++)
+                {
+                    for (int y = 0; y < yExtent; y++)
+                    {
+                        var color = (byte)(noiseValue[x, y, 0] * 255);
+                        image[x, y] = new SixLabors.ImageSharp.PixelFormats.Rgb24(color, color, color);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestPerlinNoise3DPerformance()
+        {
+            if (!Vector.IsHardwareAccelerated)
+                throw new NotSupportedException();
+
+            var noise = new PerlinNoise(100);
+            for (int i = 0; i < 100_0000; i++)
+            {
+                noise.Noise(i, i, i);
+            }
+        }
+
+        [Fact]
+        public void TestPerlinNoise3DPerformanceArray()
+        {
+            if (!Vector.IsHardwareAccelerated)
+                throw new NotSupportedException();
+
+            var noiseValue = new float[100, 100, 10];
+            var noise = new PerlinNoise(100);
+            for (int i = 0; i < 100; i++)
+            {
+                noise.Noise(noiseValue, new Vector3(i, i, i), new Vector3(0.1f, 0.1f, 0.1f));
+            }
+        }
+    }
+}

+ 146 - 0
tests/UnitTest/PositionTest.cs

@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MineCase.Serialization;
+using MineCase.World;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class PositionTest
+    {
+        private readonly BlockWorldPos _bwPos1 = new BlockWorldPos(0, 0, 0);
+        private readonly BlockWorldPos _bwPos2 = new BlockWorldPos(1, 1, 1);
+        private readonly BlockWorldPos _bwPos3 = new BlockWorldPos(17, 17, 17);
+        private readonly BlockWorldPos _bwPos4 = new BlockWorldPos(-1, 1, -1);
+        private readonly BlockWorldPos _bwPos5 = new BlockWorldPos(-17, 17, -17);
+
+        private readonly BlockChunkPos _bcPos1 = new BlockChunkPos(0, 0, 0);
+        private readonly BlockChunkPos _bcPos2 = new BlockChunkPos(1, 1, 1);
+        private readonly BlockChunkPos _bcPos3 = new BlockChunkPos(1, 17, 1);
+        private readonly BlockChunkPos _bcPos4 = new BlockChunkPos(15, 1, 15);
+        private readonly BlockChunkPos _bcPos5 = new BlockChunkPos(15, 17, 15);
+
+        private readonly ChunkWorldPos _cwPos1 = new ChunkWorldPos(0, 0);
+        private readonly ChunkWorldPos _cwPos2 = new ChunkWorldPos(0, 0);
+        private readonly ChunkWorldPos _cwPos3 = new ChunkWorldPos(1, 1);
+        private readonly ChunkWorldPos _cwPos4 = new ChunkWorldPos(-1, -1);
+        private readonly ChunkWorldPos _cwPos5 = new ChunkWorldPos(-2, -2);
+
+        private readonly BlockWorldPos _cbwPos1 = new BlockWorldPos(0, 0, 0);
+        private readonly BlockWorldPos _cbwPos2 = new BlockWorldPos(0, 0, 0);
+        private readonly BlockWorldPos _cbwPos3 = new BlockWorldPos(16, 0, 16);
+        private readonly BlockWorldPos _cbwPos4 = new BlockWorldPos(-15, 0, -15);
+        private readonly BlockWorldPos _cbwPos5 = new BlockWorldPos(-31, 0, -31);
+
+        private readonly EntityWorldPos _ewPos1 = new EntityWorldPos(0, 0, 0);
+        private readonly EntityWorldPos _ewPos2 = new EntityWorldPos(1.5f, 1.5f, 1.5f);
+        private readonly EntityWorldPos _ewPos3 = new EntityWorldPos(17.5f, 17.5f, 17.5f);
+        private readonly EntityWorldPos _ewPos4 = new EntityWorldPos(-0.5f, 1.5f, -0.5f);
+        private readonly EntityWorldPos _ewPos5 = new EntityWorldPos(-16.5f, 17.5f, -16.5f);
+
+        private readonly EntityChunkPos _ecPos1 = new EntityChunkPos(0, 0, 0);
+        private readonly EntityChunkPos _ecPos2 = new EntityChunkPos(1.5f, 1.5f, 1.5f);
+        private readonly EntityChunkPos _ecPos3 = new EntityChunkPos(1.5f, 17.5f, 1.5f);
+        private readonly EntityChunkPos _ecPos4 = new EntityChunkPos(15.5f, 1.5f, 15.5f);
+        private readonly EntityChunkPos _ecPos5 = new EntityChunkPos(15.5f, 17.5f, 15.5f);
+
+        [Fact]
+        public void TestBlockWorldPos()
+        {
+            Assert.Equal(_bcPos1, _bwPos1.ToBlockChunkPos());
+            Assert.Equal(_bcPos2, _bwPos2.ToBlockChunkPos());
+            Assert.Equal(_bcPos3, _bwPos3.ToBlockChunkPos());
+            Assert.Equal(_bcPos4, _bwPos4.ToBlockChunkPos());
+            Assert.Equal(_bcPos5, _bwPos5.ToBlockChunkPos());
+
+            Assert.Equal(_cwPos1, _bwPos1.ToChunkWorldPos());
+            Assert.Equal(_cwPos2, _bwPos2.ToChunkWorldPos());
+            Assert.Equal(_cwPos3, _bwPos3.ToChunkWorldPos());
+            Assert.Equal(_cwPos4, _bwPos4.ToChunkWorldPos());
+            Assert.Equal(_cwPos5, _bwPos5.ToChunkWorldPos());
+        }
+
+        [Fact]
+        public void TestBlockChunkPos()
+        {
+            Assert.Equal(_bwPos1, _bcPos1.ToBlockWorldPos(_cwPos1));
+            Assert.Equal(_bwPos2, _bcPos2.ToBlockWorldPos(_cwPos2));
+            Assert.Equal(_bwPos3, _bcPos3.ToBlockWorldPos(_cwPos3));
+            Assert.Equal(_bwPos4, _bcPos4.ToBlockWorldPos(_cwPos4));
+            Assert.Equal(_bwPos5, _bcPos5.ToBlockWorldPos(_cwPos5));
+        }
+
+        [Fact]
+        public void TestChunkWorldPos()
+        {
+            Assert.Equal(_cbwPos1, _cwPos1.ToBlockWorldPos());
+            Assert.Equal(_cbwPos2, _cwPos2.ToBlockWorldPos());
+            Assert.Equal(_cbwPos3, _cwPos3.ToBlockWorldPos());
+            Assert.Equal(_cbwPos4, _cwPos4.ToBlockWorldPos());
+            Assert.Equal(_cbwPos5, _cwPos5.ToBlockWorldPos());
+        }
+
+        [Fact]
+        public void TestEntityWorldPos_ToChunkWorldPos()
+        {
+            Assert.Equal(_cwPos1, _ewPos1.ToChunkWorldPos());
+            Assert.Equal(_cwPos2, _ewPos2.ToChunkWorldPos());
+            Assert.Equal(_cwPos3, _ewPos3.ToChunkWorldPos());
+            Assert.Equal(_cwPos4, _ewPos4.ToChunkWorldPos());
+            Assert.Equal(_cwPos5, _ewPos5.ToChunkWorldPos());
+        }
+
+        [Fact]
+        public void TestEntityWorldPos_ToBlockWorldPos()
+        {
+            Assert.Equal(_bwPos1, _ewPos1.ToBlockWorldPos());
+            Assert.Equal(_bwPos2, _ewPos2.ToBlockWorldPos());
+            Assert.Equal(_bwPos3, _ewPos3.ToBlockWorldPos());
+            Assert.Equal(_bwPos4, _ewPos4.ToBlockWorldPos());
+            Assert.Equal(_bwPos5, _ewPos5.ToBlockWorldPos());
+        }
+
+        [Fact]
+        public void TestEntityWorldPos_ToEntityChunkPos()
+        {
+            Assert.Equal(_ecPos1, _ewPos1.ToEntityChunkPos());
+            Assert.Equal(_ecPos2, _ewPos2.ToEntityChunkPos());
+            Assert.Equal(_ecPos3, _ewPos3.ToEntityChunkPos());
+            Assert.Equal(_ecPos4, _ewPos4.ToEntityChunkPos());
+            Assert.Equal(_ecPos5, _ewPos5.ToEntityChunkPos());
+        }
+
+        [Fact]
+        public void TestEntityChunkPos_ToEntityWorldPos()
+        {
+            Assert.Equal(_ewPos1, _ecPos1.ToEntityWorldPos(_cwPos1));
+            Assert.Equal(_ewPos2, _ecPos2.ToEntityWorldPos(_cwPos2));
+            Assert.Equal(_ewPos3, _ecPos3.ToEntityWorldPos(_cwPos3));
+            Assert.Equal(_ewPos4, _ecPos4.ToEntityWorldPos(_cwPos4));
+            Assert.Equal(_ewPos5, _ecPos5.ToEntityWorldPos(_cwPos5));
+        }
+
+        [Fact]
+        public void TestPositionReadWrites()
+        {
+            TestPositionReadWrite(_bwPos1);
+            TestPositionReadWrite(_bwPos2);
+            TestPositionReadWrite(_bwPos3);
+            TestPositionReadWrite(_bwPos4);
+            TestPositionReadWrite(_bwPos5);
+        }
+
+        private void TestPositionReadWrite(Position position)
+        {
+            using (var mem = new MemoryStream())
+            using (var bw = new BinaryWriter(mem, Encoding.UTF8, true))
+            {
+                bw.WriteAsPosition(position);
+                var br = new SpanReader(mem.ToArray());
+                Assert.Equal(position, br.ReadAsPosition());
+            }
+        }
+    }
+}

+ 184 - 0
tests/UnitTest/RNGTest.cs

@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+using MineCase.Algorithm;
+using SixLabors.ImageSharp;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class RNGTest
+    {
+        public readonly string RootDir;
+
+        public RNGTest()
+        {
+            RootDir = SetRootDir();
+        }
+
+        private static string SetRootDir([CallerFilePath]string fileName = null) =>
+            Path.Combine(Path.GetDirectoryName(fileName), "bin");
+
+        [Fact]
+        public void TestNext()
+        {
+            var rng = new UniformRNG(0);
+            int first = rng.NextInt32(), second = rng.NextInt32();
+            for (int i = 0; i < 1000000; ++i)
+            {
+                first = second;
+                second = rng.NextInt32();
+                Assert.NotEqual(first, second);
+            }
+        }
+
+        [Fact]
+        public void TestUniform()
+        {
+            var rng = new UniformRNG(0);
+
+            var i1 = rng.Uniform(1, 2);
+            Assert.Equal(1, i1);
+            var i2 = rng.Uniform(1U, 2U);
+            Assert.Equal(1U, i2);
+            var i3 = rng.Uniform(1.0f, 1.0f);
+            Assert.Equal(1.0f, i3);
+            var i4 = rng.Uniform(1.0, 1.0);
+            Assert.Equal(1.0, i4);
+        }
+
+        [Fact]
+        public void TestFrequencyImg()
+        {
+            var rng = new UniformRNG(0);
+            int[] bucket = new int[100];
+
+            const int xExtent = 100;
+            const int yExtent = 200;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "rng_frequency.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                for (int i = 0; i < 10000; ++i)
+                {
+                    bucket[rng.Uniform(0, xExtent)]++;
+                }
+
+                for (int i = 0; i < xExtent; ++i)
+                {
+                    for (int j = 0; j < bucket[i]; ++j)
+                    {
+                        image[i, yExtent - j - 1] = new SixLabors.ImageSharp.PixelFormats.Rgb24(0xFF, 0x69, 0xB4);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestIntNoiseImg()
+        {
+            var rng = new UniformRNG(0);
+
+            const int xExtent = 100;
+            const int yExtent = 100;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "rng_int_noise.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                for (int i = 0; i < xExtent; ++i)
+                {
+                    for (int j = 0; j < yExtent; ++j)
+                    {
+                        var color = (byte)rng.Uniform(0, 255);
+                        image[i, j] = new SixLabors.ImageSharp.PixelFormats.Rgb24(color, color, color);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestFloatNoiseImg()
+        {
+            var rng = new UniformRNG(0);
+
+            const int xExtent = 100;
+            const int yExtent = 100;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "rng_float_noise.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                for (int i = 0; i < xExtent; ++i)
+                {
+                    for (int j = 0; j < yExtent; ++j)
+                    {
+                        var color = (byte)(rng.Uniform(0.0f, 2.55f) * 100);
+                        image[i, j] = new SixLabors.ImageSharp.PixelFormats.Rgb24(color, color, color);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestDoubleNoiseImg()
+        {
+            var rng = new UniformRNG(0);
+
+            const int xExtent = 100;
+            const int yExtent = 100;
+
+            using (var file = File.OpenWrite(Path.Combine(RootDir, "rng_double_noise.bmp")))
+            using (var image = new Image<SixLabors.ImageSharp.PixelFormats.Rgb24>(xExtent, yExtent))
+            {
+                for (int i = 0; i < xExtent; ++i)
+                {
+                    for (int j = 0; j < yExtent; ++j)
+                    {
+                        var color = (byte)(rng.Uniform(0.0, 2.55) * 100);
+                        image[i, j] = new SixLabors.ImageSharp.PixelFormats.Rgb24(color, color, color);
+                    }
+                }
+
+                image.SaveAsBmp(file);
+            }
+        }
+
+        [Fact]
+        public void TestRNGPerformance()
+        {
+            var rng = new UniformRNG(0);
+            var random = new Random();
+            Stopwatch sw = new Stopwatch();
+
+            sw.Start();
+            int res = 0;
+            for (int i = 0; i < 10000000; ++i)
+            {
+                res += rng.NextInt32();
+            }
+
+            sw.Stop();
+            var t1 = sw.ElapsedMilliseconds;
+
+            sw.Start();
+            res = 0;
+            for (int i = 0; i < 10000000; ++i)
+            {
+                res += random.Next();
+            }
+
+            sw.Stop();
+            var t2 = sw.ElapsedMilliseconds;
+
+            Assert.True(t1 < t2);
+        }
+    }
+}

+ 101 - 0
tests/UnitTest/SerializationTest.cs

@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Engine;
+using MineCase.Engine.Serialization;
+using MineCase.Graphics;
+using MineCase.Serialization.Serializers;
+using MongoDB.Bson;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+    public class SerializationTest
+    {
+        public DependencyProperty<Slot> SlotProperty = DependencyProperty.Register<Slot>("Slot", typeof(SerializationTest), new PropertyMetadata<Slot>(Slot.Empty));
+        public DependencyProperty<Shape> ShapeProperty = DependencyProperty.Register<Shape>("Shape", typeof(SerializationTest));
+        public DependencyProperty<Cuboid> CuboidProperty = DependencyProperty.Register<Cuboid>("Cuboid", typeof(SerializationTest));
+        public DependencyProperty<StateHolder> StateProperty = DependencyProperty.Register<StateHolder>("State", typeof(SerializationTest));
+
+        public class Pair
+        {
+            public int Int { get; set; }
+
+            public Shape Collider { get; set; }
+        }
+
+        public class StateHolder
+        {
+            public List<Pair> Shape { get; set; }
+        }
+
+        public SerializationTest()
+        {
+            Serializers.RegisterAll();
+        }
+
+        [Fact]
+        public async Task Test1()
+        {
+            var slot = new Slot { BlockId = 1, ItemDamage = 2, ItemCount = 3 };
+            var shape = new Cuboid(new Point3d(0, 1, 2), new Size(10, 20, 30));
+            var state = new StateHolder { Shape = new List<Pair> { new Pair { Collider = shape } } };
+
+            var entity = new TestEntity();
+            await entity.ReadStateAsync();
+            entity.SetCurrentValue(SlotProperty, slot);
+            entity.SetCurrentValue(ShapeProperty, shape);
+            entity.SetCurrentValue(CuboidProperty, shape);
+            entity.SetCurrentValue(StateProperty, state);
+
+            var doc = Serialize(entity);
+
+            Assert.Equal(2, doc.ElementCount);
+            var vs = doc.GetElement(1);
+            Assert.Equal("ValueStorage", vs.Name);
+            var vsv = (BsonDocument)vs.Value;
+            Assert.Equal(4, vsv.ElementCount);
+
+            entity = new TestEntity();
+            entity.BsonDocument = doc;
+            await entity.ReadStateAsync();
+            Assert.Equal(4, entity.ValueStorage.Keys.Count());
+            Assert.Equal(slot, entity.GetValue(SlotProperty));
+            Assert.Equal(shape, entity.GetValue(ShapeProperty));
+            Assert.Equal(shape, entity.GetValue(CuboidProperty));
+            Assert.Equal(shape, entity.GetValue(StateProperty).Shape[0].Collider);
+        }
+
+        private BsonDocument Serialize(TestEntity entity)
+        {
+            var doc = new BsonDocument();
+            var writer = new BsonDocumentWriter(doc);
+            var context = BsonSerializationContext.CreateRoot(writer);
+            var serializer = new DependencyObjectStateSerializer();
+            serializer.Serialize(context, new DependencyObjectState
+            {
+                GrainKeyString = "test",
+                ValueStorage = entity.ValueStorage
+            });
+            return doc;
+        }
+
+        internal class TestEntity : DependencyObject
+        {
+            public BsonDocument BsonDocument;
+
+            protected override Task<DependencyObjectState> DeserializeStateAsync()
+            {
+                if (BsonDocument == null) return base.DeserializeStateAsync();
+                var reader = new BsonDocumentReader(BsonDocument);
+                var serializer = new DependencyObjectStateSerializer();
+                var context = BsonDeserializationContext.CreateRoot(reader);
+                return Task.FromResult(serializer.Deserialize(context));
+            }
+        }
+    }
+}