1 /** 2 * This module implements functionality helpful for writing integration tests 3 * as opposed to the unit variety where unit-tests are defined as not 4 * having global side-effects. In constrast, this module implements 5 * assertions that check for global side-effects such as writing to the 6 * file system. 7 */ 8 9 module unit_threaded.integration; 10 11 import unit_threaded.should; 12 13 extern(C) char* mkdtemp(char*); 14 15 shared static this() { 16 import std.file; 17 if(!Sandbox.sandboxPath.exists) return; 18 19 foreach(entry; dirEntries(Sandbox.sandboxPath, SpanMode.shallow)) { 20 if(isDir(entry.name)) { 21 rmdirRecurse(entry); 22 } 23 } 24 } 25 26 27 @safe: 28 29 /** 30 Responsible for creating a temporary directory to serve as a sandbox where 31 files can be created, written to or deleted. 32 */ 33 struct Sandbox { 34 import std.path; 35 36 enum defaultSandboxPath = buildPath("tmp", "unit-threaded"); 37 static string sandboxPath = defaultSandboxPath; 38 string testPath; 39 40 /// Instantiate a Sandbox object 41 static Sandbox opCall() { 42 Sandbox ret; 43 ret.testPath = newTestDir; 44 return ret; 45 } 46 47 /// 48 unittest { 49 auto sb = Sandbox(); 50 assert(sb.testPath != ""); 51 } 52 53 static void setPath(string path) { 54 import std.file; 55 sandboxPath = path; 56 if(!sandboxPath.exists) () @trusted { mkdirRecurse(sandboxPath); }(); 57 } 58 59 /// 60 unittest { 61 import std.file; 62 import std.path; 63 64 Sandbox.sandboxPath.shouldEqual(defaultSandboxPath); 65 66 immutable newPath = buildPath("foo", "bar", "baz"); 67 assert(!newPath.exists); 68 Sandbox.setPath(newPath); 69 assert(newPath.exists); 70 scope(exit) () @trusted { rmdirRecurse("foo"); }(); 71 Sandbox.sandboxPath.shouldEqual(newPath); 72 73 with(immutable Sandbox()) { 74 writeFile("newPath.txt"); 75 assert(buildPath(newPath, testPath, "newPath.txt").exists); 76 } 77 78 Sandbox.resetPath; 79 Sandbox.sandboxPath.shouldEqual(defaultSandboxPath); 80 } 81 82 static void resetPath() { 83 sandboxPath = defaultSandboxPath; 84 } 85 86 /// Write a file to the sandbox 87 void writeFile(in string fileName, in string output = "") const { 88 import std.stdio; 89 import std.path; 90 File(buildPath(testPath, fileName), "w").writeln(output); 91 } 92 93 /// Write a file to the sanbox 94 void writeFile(in string fileName, in string[] lines) const { 95 import std.array; 96 writeFile(fileName, lines.join("\n")); 97 } 98 99 /// 100 unittest { 101 import std.file; 102 import std.path; 103 104 with(immutable Sandbox()) { 105 assert(!buildPath(testPath, "foo.txt").exists); 106 writeFile("foo.txt"); 107 assert(buildPath(testPath, "foo.txt").exists); 108 } 109 } 110 111 /// Assert that a file exists in the sandbox 112 void shouldExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 113 import std.file; 114 import std.path; 115 fileName = buildPath(testPath, fileName); 116 if(!fileName.exists) 117 fail("Expected " ~ fileName ~ " to exist but it didn't", file, line); 118 } 119 120 /// 121 unittest { 122 with(immutable Sandbox()) { 123 shouldExist("bar.txt").shouldThrow; 124 writeFile("bar.txt"); 125 shouldExist("bar.txt"); 126 } 127 } 128 129 /// Assert that a file does not exist in the sandbox 130 void shouldNotExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 131 import std.file; 132 import std.path; 133 fileName = buildPath(testPath, fileName); 134 if(fileName.exists) 135 fail("Expected " ~ fileName ~ " to not exist but it did", file, line); 136 } 137 138 /// 139 unittest { 140 with(immutable Sandbox()) { 141 shouldNotExist("baz.txt"); 142 writeFile("baz.txt"); 143 shouldNotExist("baz.txt").shouldThrow; 144 } 145 } 146 147 /// read a file in the test sandbox and verify its contents 148 void shouldEqualLines(in string fileName, in string[] lines, 149 string file = __FILE__, size_t line = __LINE__) const { 150 import std.file; 151 import std.string; 152 153 readText(buildPath(testPath, fileName)).chomp.split("\n") 154 .shouldEqual(lines, file, line); 155 } 156 157 /// 158 unittest { 159 with(immutable Sandbox()) { 160 writeFile("lines.txt", ["foo", "toto"]); 161 shouldEqualLines("lines.txt", ["foo", "bar"]).shouldThrow; 162 shouldEqualLines("lines.txt", ["foo", "toto"]); 163 } 164 } 165 166 private: 167 168 static string newTestDir() { 169 import std.conv; 170 import std.path; 171 import std.algorithm; 172 import std.exception; 173 import std.file; 174 import core.stdc.string; 175 import core.stdc.errno; 176 177 if(!sandboxPath.exists) { 178 () @trusted { mkdirRecurse(sandboxPath); }(); 179 } 180 181 char[100] template_; 182 std.algorithm.copy(buildPath(sandboxPath, "XXXXXX") ~ '\0', template_[]); 183 184 auto ret = () @trusted { return mkdtemp(&template_[0]).to!string; }(); 185 enforce(ret != "", "Failed to create temporary directory name: " ~ 186 () @trusted { return strerror(errno).to!string; }()); 187 188 return ret.absolutePath; 189 } 190 191 }