1 2 // Copyright Ferdinand Majerech 2011 - 2012. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 8 module dgamevfs.test; 9 10 11 import dgamevfs._; 12 13 14 import std.algorithm; 15 import std.file; 16 import std.stdio; 17 import std.traits; 18 import std.typecons; 19 20 21 //collectException has some kind of bug (probably lazy expressions), 22 //so using this instead: 23 bool fail(D)(D dg) if(isDelegate!D) 24 { 25 try{dg();} 26 catch(VFSException e){return true;} 27 return false; 28 } 29 30 bool testVFSFileInfo(VFSDir parent, string name, 31 Flag!"exists" exists, Flag!"writable" writable) 32 { 33 auto file = parent.file(name); 34 if(file.path != parent.path ~ "/" ~ name) {return false;} 35 if(file.exists != exists || file.writable != writable){return false;} 36 37 if(!exists && (!fail(&file.bytes) || !fail(&file.input))){return false;} 38 if(exists && (fail(&file.bytes) || fail(&file.input))) {return false;} 39 return true; 40 } 41 42 bool testVFSFileRead(VFSDir parent, string name, string expected) 43 { 44 auto file = parent.file(name); 45 46 VFSFileInput refcountTestIn(VFSFileInput i){return i;} 47 //read what we've written before 48 { 49 auto i = refcountTestIn(file.input); 50 auto buf = new char[expected.length * 2]; 51 auto slice = i.read(cast(void[])buf); 52 if(slice.length != file.bytes || cast(string)slice != expected) 53 { 54 return false; 55 } 56 } 57 //should be automatically closed 58 if(file.open){return false;} 59 60 return true; 61 } 62 63 bool testVFSFileIO(VFSDir parent, string name, string data) 64 { 65 auto file = parent.file(name); 66 67 VFSFileOutput refcountTestOut(VFSFileOutput o){return o;} 68 //write, this should create the file if it doesn't exist yet 69 { 70 auto o = refcountTestOut(file.output); 71 o.write(cast(const void[])data); 72 } 73 //should be automatically closed 74 if(file.open){return false;} 75 76 if(fail(&file.bytes) || file.bytes != data.length || fail(&file.input)) 77 { 78 return false; 79 } 80 81 if(!testVFSFileRead(parent, name, data)) 82 { 83 return false; 84 } 85 86 return true; 87 } 88 89 bool testVFSFileSeek(VFSDir parent, string name) 90 { 91 auto file = parent.file(name); 92 { 93 auto output = file.output; 94 output.write(cast(const void[])"Teh smdert iz"); 95 if(!fail({output.seek(-4, Seek.Set);}) || 96 !fail({output.seek(4, Seek.End);})) 97 { 98 return false; 99 } 100 101 output.seek(0, Seek.Set); 102 output.write(cast(const void[])"The"); 103 output.write(cast(const void[])" answaa"); 104 output.seek(-2, Seek.Current); 105 output.write(cast(const void[])"er"); 106 output.seek(-2, Seek.End); 107 output.write(cast(const void[])"is 42."); 108 } 109 { 110 auto input = file.input; 111 if(!fail({input.seek(-4, Seek.Set);}) || 112 !fail({input.seek(4, Seek.End);})) 113 { 114 return false; 115 } 116 117 auto buffer = new char[3]; 118 input.read(cast(void[])buffer); 119 if(buffer != "The"){return false;} 120 121 input.seek(-3, Seek.End); 122 input.read(cast(void[])buffer); 123 if(buffer != "42."){return false;} 124 125 input.seek(0, Seek.Set); 126 buffer = new char[cast(uint)file.bytes]; 127 input.read(cast(void[])buffer); 128 if(buffer != "The answer is 42."){return false;} 129 } 130 return true; 131 } 132 133 bool testVFSDirInfo(VFSDir parent, string name, Flag!"exists" exists, 134 Flag!"writable" writable, Flag!"create" create) 135 { 136 auto dir = parent.dir(name); 137 if(dir.path != parent.path ~ "/" ~ name) {return false;} 138 if(dir.exists != exists || dir.writable != writable){return false;} 139 if(!create){return true;} 140 dir.create(); 141 if(!dir.exists){return false;} 142 return true; 143 } 144 145 bool testVFSDirFiles(VFSDir dir, Flag!"deep" deep) 146 { 147 foreach(file; dir.files(deep)) 148 { 149 auto sepsAdded = count(file.path, '/') - count(dir.path, '/'); 150 if(sepsAdded < 1){return false;} 151 if(!file.exists){return false;} 152 } 153 return true; 154 } 155 156 bool testVFSDirDirs(VFSDir dir, Flag!"deep" deep) 157 { 158 foreach(sub; dir.dirs(deep)) 159 { 160 auto sepsAdded = count(sub.path, '/') - count(dir.path, '/'); 161 if(sepsAdded < 1){return false;} 162 if(!sub.exists){return false;} 163 } 164 return true; 165 } 166 167 bool testVFSDirContents(VFSDir dir, string[] expectedFiles, string[] expectedDirs, string glob = null) 168 { 169 alias std.algorithm.remove remove; 170 foreach(file; dir.files(Yes.deep, glob)) 171 { 172 if(!canFind(expectedFiles, file.path)) 173 { 174 writeln("FAILED file iteration (unexpected file ", file.path, ")"); 175 //Unexpected file. 176 return false; 177 } 178 expectedFiles = remove!((string a){return a == file.path;})(expectedFiles); 179 } 180 foreach(sub; dir.dirs(Yes.deep, glob)) 181 { 182 if(!canFind(expectedDirs, sub.path)) 183 { 184 writeln("FAILED directory iteration (unexpected directory ", sub.path, ")"); 185 //Unexpected directory. 186 return false; 187 } 188 expectedDirs = remove!((string a){return a == sub.path;})(expectedDirs); 189 } 190 if(expectedFiles.length > 0 || expectedDirs.length > 0) 191 { 192 writeln("FAILED file/directory iteration (unexpected files, dirs: ", 193 expectedFiles, expectedDirs, ")"); 194 //Missing file/directory. 195 return false; 196 } 197 return true; 198 } 199 200 bool testVFSDirGlob(VFSDir root) 201 { 202 assert(root.path == "root"); 203 auto dirsCreate = ["fonts", 204 "fonts/ttf", 205 "fonts/otf", 206 "fonts/extra", 207 "shaders", 208 "shaders/extra", 209 "txt"]; 210 auto filesCreate = ["fonts/config.txt", 211 "fonts/ttf/a.ttf", 212 "fonts/ttf/info.txt", 213 "fonts/ttf/b.ttf", 214 "fonts/otf/a.otf", 215 "fonts/otf/otf.txt", 216 "fonts/extra/bitmapfont.png", 217 "shaders/extra/info.txt", 218 "shaders/extra/effect.frag", 219 "shaders/extra/effect.vert"]; 220 with(root) 221 { 222 foreach(subdir; dirsCreate) 223 { 224 dir(subdir).create(); 225 } 226 foreach(subfile; filesCreate) 227 { 228 file(subfile).output.write(cast(const void[])"42"); 229 } 230 231 //Containing font: 232 auto fontDirs = ["root/fonts", 233 "root/fonts/ttf", 234 "root/fonts/otf", 235 "root/fonts/extra"]; 236 auto fontFiles = ["root/fonts/config.txt", 237 "root/fonts/ttf/a.ttf", 238 "root/fonts/ttf/info.txt", 239 "root/fonts/ttf/b.ttf", 240 "root/fonts/otf/a.otf", 241 "root/fonts/otf/otf.txt", 242 "root/fonts/extra/bitmapfont.png"]; 243 244 //Containing extra: 245 auto extraDirs = ["root/fonts/extra", "root/shaders/extra"]; 246 auto extraFiles = ["root/fonts/extra/bitmapfont.png", 247 "root/shaders/extra/info.txt", 248 "root/shaders/extra/effect.frag", 249 "root/shaders/extra/effect.vert"]; 250 251 //Ending with ttf: 252 auto ttfDirs = ["root/fonts/ttf"]; 253 auto ttfFiles = ["root/fonts/ttf/a.ttf", "root/fonts/ttf/b.ttf"]; 254 255 //Ending with .txt: 256 auto txtDirs = cast(string[])[]; 257 auto txtFiles = ["root/fonts/config.txt", 258 "root/fonts/ttf/info.txt", 259 "root/fonts/otf/otf.txt", 260 "root/shaders/extra/info.txt"]; 261 262 //Containing tf: 263 auto tfDirs = ["root/fonts/ttf", "root/fonts/otf"]; 264 auto tfFiles = ["root/fonts/ttf/a.ttf", 265 "root/fonts/ttf/info.txt", 266 "root/fonts/ttf/b.ttf", 267 "root/fonts/otf/a.otf", 268 "root/fonts/otf/otf.txt"]; 269 270 if(!testVFSDirContents(root, fontFiles, fontDirs, "*font*") || 271 !testVFSDirContents(root, extraFiles, extraDirs, "*extra*") || 272 !testVFSDirContents(root, ttfFiles, ttfDirs, "*ttf") || 273 !testVFSDirContents(root, txtFiles, txtDirs, "*.txt") || 274 !testVFSDirContents(root, tfFiles, tfDirs, "*tf*")) 275 { 276 return false; 277 } 278 } 279 280 return true; 281 } 282 283 bool testVFSDirMain(VFSDir root) 284 { 285 //We expect these to be true from the start. 286 if(!root.exists || !root.writable || root.path != "root"){return false;} 287 288 string answer = "The answer is 42."; 289 290 //Nonexistent file: 291 if(!testVFSFileInfo(root, "file1", No.exists, Yes.writable) || 292 !testVFSFileIO(root, "file1", answer)) 293 { 294 writeln("FAILED nonexistent file"); 295 return false; 296 } 297 298 //Direct file access: (file1 exists now) 299 if(!testVFSFileInfo(root, "file1", Yes.exists, Yes.writable) || 300 !testVFSFileRead(root, "file1", answer)) 301 { 302 writeln("FAILED existing file"); 303 return false; 304 } 305 306 //Invalid file paths: 307 if(!fail({root.file("a::c");}) || !fail({root.file("nonexistent_subdir/file");})) 308 { 309 writeln("FAILED invalid file paths"); 310 return false; 311 } 312 313 //Creating subdirectories: 314 if(!testVFSDirInfo(root, "sub1", No.exists, Yes.writable, Yes.create) || 315 !testVFSDirInfo(root, "sub1/sub2", No.exists, Yes.writable, Yes.create)) 316 { 317 writeln("FAILED creating subdirectories"); 318 return false; 319 } 320 foreach(char c; 'A' .. 'Z') 321 { 322 auto name = "sub1/sub2/sub" ~ c; 323 if(!testVFSDirInfo(root, name, No.exists, Yes.writable, Yes.create)) 324 { 325 writeln("FAILED creating many subdirectories"); 326 return false; 327 } 328 auto sub = root.dir(name); 329 foreach(char f; '1' .. '9') 330 { 331 auto fname = "file" ~ f; 332 if(!testVFSFileInfo(sub, fname, No.exists, Yes.writable) || 333 !testVFSFileIO(sub, fname, answer)) 334 { 335 writeln("FAILED creating files in subdirectories"); 336 return false; 337 } 338 } 339 } 340 341 //File in a subdirectory: 342 if(!testVFSFileInfo(root, "sub1/sub2/subN/file5", Yes.exists, Yes.writable) || 343 !testVFSFileIO(root, "sub1/sub2/subN/file5", answer)) 344 { 345 writeln("FAILED file in a subdirectory"); 346 return false; 347 } 348 349 //Seeking: 350 if(!testVFSFileSeek(root, "seek_test")) 351 { 352 writeln("FAILED file seeking"); 353 return false; 354 } 355 356 //Subdirectory: 357 { 358 auto sub = root.dir("subdir"); 359 //sub doesn't exist yet: 360 if(!fail({sub.file("file");})) 361 { 362 writeln("FAILED subdirectory file/dir access"); 363 return false; 364 } 365 sub.create(); 366 //Now it exists: 367 if(fail({sub.file("file");})) 368 { 369 writeln("FAILED subdirectory file/dir access"); 370 return false; 371 } 372 //Looking for a file in a subdir of sub that doesn't exist: 373 if(!fail({sub.file("subdir2/file");})) 374 { 375 writeln("FAILED subdirectory file/dir access"); 376 return false; 377 } 378 } 379 380 //files()/dirs(): 381 if(!testVFSDirFiles(root, No.deep) || !testVFSDirFiles(root, Yes.deep) || 382 !testVFSDirDirs(root, No.deep) || !testVFSDirDirs(root, Yes.deep)) 383 { 384 writeln("FAILED file/directory iteration"); 385 return false; 386 } 387 388 //Globbing: 389 if(!testVFSDirGlob(root)) 390 { 391 writeln("FAILED globbing"); 392 return false; 393 } 394 395 //created before 396 auto sub = root.dir("sub1"); 397 //files()/dirs() from a subdir: 398 if(!testVFSDirFiles(sub, No.deep) || !testVFSDirFiles(sub, Yes.deep) || 399 !testVFSDirDirs(sub, No.deep) || !testVFSDirDirs(sub, Yes.deep)) 400 { 401 writeln("FAILED file/directory iteration in a subdirectory"); 402 return false; 403 } 404 405 //We added some files to subdirs, so files().length should be less that files(Yes.deep).length: 406 if(root.files().length >= root.files(Yes.deep).length || 407 root.dirs().length >= root.dirs(Yes.deep).length) 408 { 409 writeln("FAILED file/directory iteration item count"); 410 return false; 411 } 412 413 //Nonexistent dir: 414 { 415 auto dir = root.dir("nonexistent"); 416 if(!fail({dir.file("file");}) || 417 !fail({dir.dir("dir");}) || 418 !fail({dir.files();}) || 419 !fail({dir.dirs();})) 420 { 421 writeln("FAILED nonexistent directory"); 422 return false; 423 } 424 } 425 426 return true; 427 } 428 429 bool testMemoryDir() 430 { 431 auto memoryDir = new MemoryDir("root"); 432 //basic info methods 433 if(!memoryDir.writable || memoryDir.exists || 434 memoryDir.path != memoryDir.name || memoryDir.path != "root") 435 { 436 writeln("FAILED MemoryDir info"); 437 return false; 438 } 439 440 //create 441 memoryDir.create(); 442 if(!memoryDir.exists) 443 { 444 writeln("FAILED MemoryDir create"); 445 return false; 446 } 447 448 if(!testVFSDirMain(memoryDir)) 449 { 450 writeln("FAILED MemoryDir general"); 451 return false; 452 } 453 454 //remove 455 memoryDir.dir("subdir").create(); 456 memoryDir.remove(); 457 if(memoryDir.exists) 458 { 459 writeln("FAILED memoryDir remove"); 460 return false; 461 } 462 return true; 463 } 464 465 bool testStackDirMount() 466 { 467 auto cycle1 = new StackDir("cycle1"); 468 auto cycle2 = new StackDir("cycle2"); 469 cycle1.mount(cycle2); 470 if(!fail({cycle2.mount(cycle1);})) 471 { 472 writeln("FAILED circular mounting"); 473 return false; 474 } 475 476 auto parent = new StackDir("parent"); 477 auto child = new MemoryDir("child"); 478 auto main = new StackDir("main"); 479 parent.mount(child); 480 if(fail({main.mount(child);})) 481 { 482 writeln("FAILED mounting a dir with parent"); 483 return false; 484 } 485 486 auto child2 = new StackDir("child"); 487 if(!fail({parent.mount(child2);})) 488 { 489 writeln("FAILED mounting a dir with same name"); 490 return false; 491 } 492 493 return true; 494 } 495 496 bool testStackDirStacking() 497 { 498 //Initialize directory structure: 499 auto mainPkg = new MemoryDir("main"); 500 with(mainPkg) 501 { 502 create(); 503 dir("fonts").create(); 504 dir("fonts/ttf").create(); 505 dir("fonts/otf").create(); 506 file("fonts/ttf/a.ttf").output.write(cast(const void[])"42"); 507 file("fonts/otf/b.otf").output.write(cast(const void[])"42"); 508 dir("logs").create(); 509 dir("shaders").create(); 510 file("shaders/font.vert").output.write(cast(const void[])"42"); 511 file("shaders/font.frag").output.write(cast(const void[])"42"); 512 writable = false; 513 } 514 515 auto userPkg = new MemoryDir("user"); 516 with(userPkg) 517 { 518 create(); 519 dir("fonts").create(); 520 dir("fonts/ttf").create(); 521 file("fonts/ttf/a.ttf").output.write(cast(const void[])"42"); 522 dir("logs").create(); 523 dir("shaders").create(); 524 dir("maps").create(); 525 } 526 527 auto modPkg = new MemoryDir("mod"); 528 auto modLog = "mod/logs/memory.log"; 529 with(modPkg) 530 { 531 create(); 532 dir("logs").create(); 533 dir("shaders").create(); 534 file("shaders/font.vert").output.write(cast(const void[])"42"); 535 file("logs/memory.log").output.write(cast(const void[])modLog); 536 writable = false; 537 } 538 539 auto stackDir = new StackDir("root"); 540 stackDir.mount(mainPkg); 541 stackDir.mount(userPkg); 542 stackDir.mount(modPkg); 543 544 if(!stackDir.writable || !stackDir.exists) 545 { 546 writeln("FAILED stackdir info"); 547 return false; 548 } 549 550 //File/dir access: 551 { 552 //Should find the right file to get bytes() and read from: 553 if(!testVFSFileRead(stackDir.dir("logs"), "memory.log", modLog)) 554 { 555 writeln("FAILED stackdir read"); 556 return false; 557 } 558 //This should write to the "user" package. 559 stackDir.dir("logs").file("memory.log").output.write(cast(const void[])"out"); 560 //This should read from the "mod" package 561 if(!testVFSFileRead(stackDir.dir("logs"), "memory.log", modLog)) 562 { 563 writeln("FAILED stackdir read shadowing written"); 564 return false; 565 } 566 if(!testVFSFileRead(stackDir.dir("user::logs"), "memory.log", "out")) 567 { 568 writeln("FAILED stackdir explicitly read written"); 569 return false; 570 } 571 } 572 573 //File/dir iteration: 574 auto expectedFiles = ["root/logs/memory.log", 575 "root/shaders/font.vert", 576 "root/shaders/font.frag", 577 "root/fonts/ttf/a.ttf", 578 "root/fonts/otf/b.otf"]; 579 auto expectedDirs = ["root/logs", 580 "root/shaders", 581 "root/maps", 582 "root/fonts", 583 "root/fonts/ttf", 584 "root/fonts/otf"]; 585 if(!testVFSDirContents(stackDir, expectedFiles, expectedDirs)) 586 { 587 writeln("FAILED file/directory iteration"); 588 return false; 589 } 590 591 expectedFiles = ["root/fonts/ttf/a.ttf", 592 "root/fonts/otf/b.otf"]; 593 expectedDirs = ["root/fonts/ttf", 594 "root/fonts/otf"]; 595 if(!testVFSDirContents(stackDir.dir("fonts"), expectedFiles, expectedDirs)) 596 { 597 writeln("FAILED file/directory iteration in a subdirectory"); 598 return false; 599 } 600 601 return true; 602 } 603 604 bool testStackDir() 605 { 606 //General: 607 { 608 auto mainPkg = new MemoryDir("main"); 609 auto userPkg = new MemoryDir("user"); 610 auto stackDir = new StackDir("root"); 611 stackDir.mount(mainPkg); 612 stackDir.mount(userPkg); 613 614 //basic info methods 615 if(!stackDir.writable || stackDir.exists || 616 stackDir.path != stackDir.name || stackDir.path != "root") 617 { 618 writeln("FAILED StackDir info"); 619 return false; 620 } 621 622 //create 623 stackDir.create(); 624 if(!stackDir.exists) 625 { 626 writeln("FAILED StackDir create"); 627 return false; 628 } 629 630 if(!testVFSDirMain(stackDir)) 631 { 632 writeln("FAILED StackDir general"); 633 return false; 634 } 635 636 //remove 637 stackDir.dir("subdir").create(); 638 stackDir.remove(); 639 if(stackDir.exists) 640 { 641 writeln("FAILED stackDir remove"); 642 return false; 643 } 644 } 645 646 //Mounting: 647 if(!testStackDirMount()) 648 { 649 writeln("FAILED StackDir mount"); 650 return false; 651 } 652 653 //Stacking: 654 if(!testStackDirStacking()) 655 { 656 writeln("FAILED StackDir stacking"); 657 return false; 658 } 659 660 return true; 661 } 662 663 bool testFSDir() 664 { 665 auto path = "testFSDir"; 666 if(exists(path)){rmdirRecurse(path);} 667 mkdir(path); 668 scope(exit){rmdirRecurse(path);} 669 670 auto fsDir = new FSDir("root", path ~ "/root", Yes.writable); 671 //basic info methods 672 if(!fsDir.writable || fsDir.exists || 673 fsDir.path != fsDir.name || fsDir.path != "root") 674 { 675 writeln(cast(bool)fsDir.writable, " ", fsDir.exists, " ", 676 fsDir.path, " ", fsDir.name); 677 writeln("FAILED FSDir info"); 678 return false; 679 } 680 681 //create 682 fsDir.create(); 683 if(!fsDir.exists) 684 { 685 writeln("FAILED FSDir create"); 686 return false; 687 } 688 689 if(!testVFSDirMain(fsDir)) 690 { 691 writeln("FAILED FSDir general"); 692 return false; 693 } 694 695 //remove 696 fsDir.dir("subdir").create(); 697 fsDir.remove(); 698 if(fsDir.exists) 699 { 700 writeln("FAILED FSDir remove"); 701 return false; 702 } 703 704 return true; 705 } 706 707 unittest 708 { 709 writeln("---------- ", 710 testMemoryDir() ? "SUCCESS" : "FAILURE", 711 " MemoryDir unittest ", "----------"); 712 writeln("---------- ", 713 testStackDir() ? "SUCCESS" : "FAILURE", 714 " StackDir unittest ", "----------"); 715 writeln("---------- ", 716 testFSDir() ? "SUCCESS" : "FAILURE", 717 " FSDir unittest ", "----------"); 718 }