1 package minecraft 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "os" 7 "path" 8 "testing" 9 "time" 10 11 "vimagination.zapto.org/minecraft/nbt" 12 ) 13 14 func testPathChunkSetGet(t *testing.T, path Path) { 15 toPlace := []nbt.Compound{ 16 nbt.Compound{ 17 addPos(0, 0, 3), //0[4] 18 addPos(1, 0, 2), //0[4],1[3] 19 addPos(2, 0, 1), //0[4],1[3],2[2] 20 addPos(3, 0, 0), //0[4],1[3],2[2],3[1] 21 addPos(20, 0, 2), //0[4],1[3],2[2],3[1],20[3] 22 addPos(0, 20, 1), //0[4],1[3],2[2],3[1],20[3],640[2] 23 addPos(-1, 0, 0), 24 addPos(0, -1, 0), 25 addPos(-1, -1, 1), 26 addPos(-3, -3, 1), 27 }, 28 nbt.Compound{ 29 addPos(0, 0, 1), //0[2],[2],1[3],2[2],3[1],20[3],640[2] 30 addPos(1, 0, 2), //0[2],[2],1[3],2[2],3[1],20[3],640[2] 31 addPos(3, 0, 1), //0[2],3[2],1[3],2[2],[1],20[3],640[2] 32 addPos(4, 0, 0), //0[2],3[2],1[3],2[2],4[1],(!)20[3],(!)640[2] | (2 + 0) << 8 | 2, (2 + 2) << 8 | 2,(2 + 4) << 8 | 3,(2 + 7) << 8 | 2, (2 + 9) << 8 | 1 33 addPos(-1, 0, 1), 34 addPos(0, -2, 1), 35 addPos(-1, -1, 0), 36 addPos(-3, -3, 0), 37 }, 38 } 39 retest := []bool{ 40 false, 41 true, 42 true, 43 false, 44 true, 45 true, 46 false, 47 true, 48 false, 49 false, 50 true, 51 true, 52 true, 53 true, 54 true, 55 true, 56 true, 57 true, 58 } 59 for num, chunkList := range toPlace { 60 if err := path.SetChunk(chunkList...); err != nil { 61 t.Fatal(err.Error()) 62 } 63 for i, chunk := range chunkList { 64 if x, z, err := chunkCoords(chunk); err != nil { 65 t.Fatal(err.Error()) 66 } else if thatChunk, err := path.GetChunk(x, z); err != nil { 67 t.Fatal(err.Error()) 68 } else if thatChunk.TagID() == 0 { 69 t.Fatalf("testPathChunkSetGet: 0-%d-%d: no chunk returned", num, i) 70 } else if !thatChunk.Equal(chunk) { 71 t.Fatalf("testPathChunkSetGet: 0-%d-%d: returned chunk not equal to set chunk, expecting: -\n%s\ngot: -\n%s", num, i, chunk.String(), thatChunk.String()) 72 } 73 } 74 } 75 for num, chunkList := range toPlace { 76 for i, chunk := range chunkList { 77 if x, z, err := chunkCoords(chunk); err != nil { 78 t.Fatal(err.Error()) 79 } else if thatChunk, err := path.GetChunk(x, z); err != nil { 80 t.Fatal(err.Error()) 81 } else if thatChunk.TagID() == 0 { 82 t.Fatalf("testPathChunkSetGet: 1-%d-%d: no chunk returned", num, i) 83 } else if thatChunk.Equal(chunk) != retest[0] { 84 if retest[0] { 85 t.Errorf("testPathChunkSetGet: 1-%d-%d: returned chunk not equal to set chunk, expecting: -\n%s\ngot: -\n%s", num, i, chunk.String(), thatChunk.String()) 86 } else { 87 t.Errorf("testPathChunkSetGet: 1-%d-%d: returned chunk equal to set chunk, expecting not equal", num, i) 88 } 89 } 90 retest = retest[1:] 91 } 92 } 93 } 94 95 func testPathLevelSetGet(t *testing.T, path Path) { 96 levelDat := nbt.NewTag("", nbt.Compound{ 97 nbt.NewTag("Beep", nbt.Compound{ 98 nbt.NewTag("SomeInt", nbt.Int(45)), 99 nbt.NewTag("SomeString", nbt.String("hello")), 100 }), 101 }) 102 if err := path.WriteLevelDat(levelDat); err != nil { 103 t.Error(err.Error()) 104 } else if newLevelDat, err := path.ReadLevelDat(); err != nil { 105 t.Error(err.Error()) 106 } else if !newLevelDat.Equal(levelDat) { 107 t.Errorf("level data doesn't match original, expecting: -\n%s\ngot: -\n%s", levelDat.String(), newLevelDat.String()) 108 } 109 } 110 111 func testPathChunkRemove(t *testing.T, path Path) { 112 toRemove := [][2]int32{ 113 {0, 20}, 114 {20, 0}, 115 {-3, -3}, 116 } 117 for num, tR := range toRemove { 118 if err := path.RemoveChunk(tR[0], tR[1]); err != nil { 119 t.Error(err.Error()) 120 } else if tC, err := path.GetChunk(tR[0], tR[1]); err != nil { 121 t.Error(err.Error()) 122 } else if tC.TagID() != 0 { 123 t.Errorf("testPathChunkRemove %d: failed to remove chunk at %d,%d", num, tR[0], tR[1]) 124 } 125 } 126 } 127 128 func testPathRegionsGet(t *testing.T, path *FilePath) { 129 regions := path.GetRegions() 130 should := [][2]int32{ 131 {-1, -1}, 132 {0, -1}, 133 {-1, 0}, 134 {0, 0}, 135 } 136 if len(regions) != len(should) { 137 t.Error("returned regions slice does not match expected") 138 } else { 139 CL: 140 for i := 0; i < len(regions); i++ { 141 for j := 0; j < len(should); j++ { 142 if regions[i][0] == should[j][0] && regions[i][1] == should[j][1] { 143 should = append(should[:j], should[j+1:]...) 144 continue CL 145 } 146 } 147 t.Error("returned regions slice does not match expected") 148 break 149 } 150 } 151 } 152 153 func addPos(x, z int32, chunkNum uint8) nbt.Tag { 154 e := chunksNBT[chunkNum].Copy() 155 f := e.Data().(nbt.Compound).Get("Level").Data().(nbt.Compound) 156 f.Set(nbt.NewTag("xPos", nbt.Int(x))) 157 f.Set(nbt.NewTag("zPos", nbt.Int(z))) 158 return e 159 } 160 161 func TestMemPath(t *testing.T) { 162 f := NewMemPath() 163 testPathChunkSetGet(t, f) 164 testPathLevelSetGet(t, f) 165 testPathChunkRemove(t, f) 166 } 167 168 func TestFilePath(t *testing.T) { 169 tempDir := t.TempDir() 170 f, err := NewFilePath(tempDir) 171 if err != nil { 172 t.Error(err.Error()) 173 } 174 if a := len(f.GetRegions()); a != 0 { 175 t.Errorf("should start with zero regions, have %d", a) 176 return 177 } 178 testPathChunkSetGet(t, f) 179 testPathLevelSetGet(t, f) 180 testPathRegionsGet(t, f) 181 testPathChunkRemove(t, f) 182 183 //Check Files 184 file, err := os.Open(path.Join(tempDir, "region", "r.0.0.mca")) 185 if err != nil { 186 t.Error(err.Error()) 187 } 188 var positions, should [1024]uint32 189 if err = binary.Read(file, binary.BigEndian, positions[:]); err != nil { 190 t.Error(err.Error()) 191 } 192 file.Close() 193 should[0] = (2+0)<<8 | 1 //pos 0 + offset(2), size 1 194 should[1] = (2+4)<<8 | 3 //pos 4 + offset(2), size 3 195 should[2] = (2+7)<<8 | 1 //pos 7 + offset(2), size 1 196 should[3] = (2+8)<<8 | 1 //pos 8 + offset(2), size 1 197 should[4] = (2+1)<<8 | 1 //pos 1 + offset(2), size 1 198 199 for i := 0; i < 1024; i++ { 200 if should[i] != positions[i] { 201 t.Errorf("chunk position/size incorrect, expecting chunk %d at %d, got %d", i, should[i], positions[i]) 202 } 203 } 204 205 //Check Defrag 206 207 err = f.Defrag(0, 0) 208 if err != nil { 209 t.Error(err.Error()) 210 return 211 } 212 213 file, err = os.Open(path.Join(tempDir, "region", "r.0.0.mca")) 214 if err != nil { 215 t.Error(err.Error()) 216 } 217 if err = binary.Read(file, binary.BigEndian, positions[:]); err != nil { 218 t.Error(err.Error()) 219 } 220 file.Close() 221 222 should[0] = (2+0)<<8 | 1 //pos 0 + offset(2), size 1 223 should[1] = (2+1)<<8 | 3 //pos 1 + offset(2), size 3 224 should[2] = (2+4)<<8 | 1 //pos 4 + offset(2), size 1 225 should[3] = (2+5)<<8 | 1 //pos 5 + offset(2), size 1 226 should[4] = (2+6)<<8 | 1 //pos 6 + offset(2), size 1 227 228 for i := 0; i < 1024; i++ { 229 if should[i] != positions[i] { 230 t.Errorf("chunk position/size incorrect, expecting chunk %d at %d, got %d", i, should[i], positions[i]) 231 } 232 } 233 234 regions := f.GetRegions() 235 236 for _, region := range regions { 237 if fi, err := os.Stat(path.Join(tempDir, "region", fmt.Sprintf("r.%d.%d.mca", region[0], region[1]))); err != nil { 238 t.Error(err.Error()) 239 } else if s := fi.Size(); s%4096 != 0 { 240 t.Errorf("regions %d,%d filesize not divisible by 4096, got %d", region[0], region[1], s) 241 } 242 } 243 244 if err = os.RemoveAll(tempDir); err != nil { 245 t.Error(err.Error()) 246 } 247 } 248 249 func TestFilePathLock(t *testing.T) { 250 251 var ( 252 err error 253 f, g *FilePath 254 ) 255 tempDir := t.TempDir() 256 if f, err = NewFilePath(tempDir); err != nil { 257 t.Error(err.Error()) 258 } 259 <-time.After(time.Millisecond * 2) 260 if g, err = NewFilePath(tempDir); err != nil { 261 t.Error(err.Error()) 262 } 263 264 <-time.After(time.Millisecond * 2) 265 266 _, err = f.GetChunks(0, 0) 267 if err == nil { 268 t.Errorf("expecting error, got nil") 269 } else if err != ErrNoLock { 270 t.Errorf("expecting NoLock error, got %q", err) 271 } 272 f.Lock() 273 274 <-time.After(time.Millisecond * 2) 275 276 _, err = g.GetChunks(0, 0) 277 if err == nil { 278 t.Errorf("expecting error, got nil") 279 } else if err != ErrNoLock { 280 t.Errorf("expecting NoLock error, got %q", err) 281 } 282 g.Lock() 283 284 <-time.After(time.Millisecond * 2) 285 286 _, err = f.GetChunks(0, 0) 287 if err == nil { 288 t.Errorf("expecting error, got nil") 289 } else if err != ErrNoLock { 290 t.Errorf("expecting NoLock error, got %q", err) 291 } 292 293 if err = os.RemoveAll(tempDir); err != nil { 294 t.Error(err.Error()) 295 } 296 } 297 298 var chunksNBT [4]nbt.Tag 299 300 func init() { 301 a, _ := newChunk(0, 0, nbt.Tag{}) 302 b, _ := newChunk(0, 0, nbt.Tag{}) 303 c, _ := newChunk(0, 0, nbt.Tag{}) 304 d, _ := newChunk(0, 0, nbt.Tag{}) 305 chunks := [4]chunk{ 306 *a, 307 *b, 308 *c, 309 *d, 310 } 311 for i := 0; i < 16; i++ { 312 for j := 0; j < 16; j++ { 313 for k := 0; k < 256; k++ { 314 if k < 8 { 315 var tick []Tick 316 if k%2 == 0 { 317 tick = []Tick{{int32(i+j+k) % 4096, 1, -1}} 318 } 319 chunks[3].SetBlock(int32(i), int32(k), int32(j), Block{ 320 uint16(i+j+k) % 4096, 321 uint8(i), 322 nbt.Compound{ 323 nbt.NewTag("testMD", nbt.Int(i*j*k)), 324 }, 325 tick, 326 }) 327 } 328 if k < 250 { 329 chunks[2].SetBlock(int32(i), int32(k), int32(j), Block{1, 0, nil, nil}) 330 } else { 331 chunks[2].SetBlock(int32(i), int32(k), int32(j), Block{ 332 1, 333 0, 334 nbt.Compound{ 335 nbt.NewTag("testMD", nbt.Int(i*j*k)), 336 }, 337 nil, 338 }) 339 } 340 } 341 chunks[1].SetBlock(int32(i), int32(j)*16, int32(j), Block{ 342 uint16(i*j + i + j), 343 uint8(i), 344 nbt.Compound{ 345 nbt.NewTag("testMD1", nbt.Int(i)), 346 nbt.NewTag("testMD2", nbt.Int(i+1)), 347 nbt.NewTag("testMD3", nbt.Int(i+2)), 348 nbt.NewTag("testMD4", nbt.Int(i+3)), 349 nbt.NewTag("testMD5", nbt.Int(i+4)), 350 }, 351 []Tick{{int32(i*j+i+j) % 4096, 1, -1}}, 352 }) 353 } 354 chunks[0].SetBlock(int32(i), int32(i), int32(i), Block{uint16(i), uint8(i), nil, nil}) 355 } 356 chunksNBT = [4]nbt.Tag{ 357 a.GetNBT(), 358 b.GetNBT(), 359 c.GetNBT(), 360 d.GetNBT(), 361 } 362 } 363