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 version(Windows) { 12 extern(C) int mkdir(char*); 13 extern(C) char* mktemp(char* template_); 14 char* mkdtemp(char* t) { 15 char* result = mktemp(t); 16 if (result is null) return null; 17 if (mkdir(result)) return null; 18 return result; 19 } 20 } else { 21 extern(C) char* mkdtemp(char* template_); 22 } 23 24 25 shared static this() { 26 import std.file; 27 if(!Sandbox.sandboxPath.exists) return; 28 29 foreach(entry; dirEntries(Sandbox.sandboxPath, SpanMode.shallow)) { 30 if(isDir(entry.name)) { 31 rmdirRecurse(entry); 32 } 33 } 34 } 35 36 37 @safe: 38 39 /** 40 Responsible for creating a temporary directory to serve as a sandbox where 41 files can be created, written to or deleted. 42 */ 43 struct Sandbox { 44 import std.path; 45 46 enum defaultSandboxPath = buildPath("tmp", "unit-threaded"); 47 static string sandboxPath = defaultSandboxPath; 48 string testPath; 49 50 /// Instantiate a Sandbox object 51 static Sandbox opCall() { 52 Sandbox ret; 53 ret.testPath = newTestDir; 54 return ret; 55 } 56 57 /// 58 @safe unittest { 59 auto sb = Sandbox(); 60 assert(sb.testPath != ""); 61 } 62 63 static void setPath(string path) { 64 import std.file; 65 sandboxPath = path; 66 if(!sandboxPath.exists) () @trusted { mkdirRecurse(sandboxPath); }(); 67 } 68 69 /// 70 @safe unittest { 71 import std.file; 72 import std.path; 73 import unit_threaded.should; 74 75 Sandbox.sandboxPath.shouldEqual(defaultSandboxPath); 76 77 immutable newPath = buildPath("foo", "bar", "baz"); 78 assert(!newPath.exists); 79 Sandbox.setPath(newPath); 80 assert(newPath.exists); 81 scope(exit) () @trusted { rmdirRecurse("foo"); }(); 82 Sandbox.sandboxPath.shouldEqual(newPath); 83 84 with(immutable Sandbox()) { 85 writeFile("newPath.txt"); 86 assert(buildPath(newPath, testPath, "newPath.txt").exists); 87 } 88 89 Sandbox.resetPath; 90 Sandbox.sandboxPath.shouldEqual(defaultSandboxPath); 91 } 92 93 static void resetPath() { 94 sandboxPath = defaultSandboxPath; 95 } 96 97 /// Write a file to the sandbox 98 void writeFile(in string fileName, in string output = "") const { 99 import std.stdio: File; 100 import std.path: buildPath, dirName; 101 import std.file: mkdirRecurse; 102 103 () @trusted { mkdirRecurse(buildPath(testPath, fileName.dirName)); }(); 104 File(buildPath(testPath, fileName), "w").writeln(output); 105 } 106 107 /// Write a file to the sanbox 108 void writeFile(in string fileName, in string[] lines) const { 109 import std.array; 110 writeFile(fileName, lines.join("\n")); 111 } 112 113 /// 114 @safe unittest { 115 import std.file; 116 import std.path; 117 118 with(immutable Sandbox()) { 119 assert(!buildPath(testPath, "foo.txt").exists); 120 writeFile("foo.txt"); 121 assert(buildPath(testPath, "foo.txt").exists); 122 } 123 } 124 125 @safe unittest { 126 import std.file: exists; 127 import std.path: buildPath; 128 129 with(immutable Sandbox()) { 130 writeFile("foo/bar.txt"); 131 assert(buildPath(testPath, "foo", "bar.txt").exists); 132 } 133 } 134 135 /// Assert that a file exists in the sandbox 136 void shouldExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 137 import std.file; 138 import std.path; 139 import unit_threaded.should: fail; 140 141 fileName = buildPath(testPath, fileName); 142 if(!fileName.exists) 143 fail("Expected " ~ fileName ~ " to exist but it didn't", file, line); 144 } 145 146 /// 147 @safe unittest { 148 with(immutable Sandbox()) { 149 import unit_threaded.should; 150 151 shouldExist("bar.txt").shouldThrow; 152 writeFile("bar.txt"); 153 shouldExist("bar.txt"); 154 } 155 } 156 157 /// Assert that a file does not exist in the sandbox 158 void shouldNotExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 159 import std.file; 160 import std.path; 161 import unit_threaded.should; 162 163 fileName = buildPath(testPath, fileName); 164 if(fileName.exists) 165 fail("Expected " ~ fileName ~ " to not exist but it did", file, line); 166 } 167 168 /// 169 @safe unittest { 170 with(immutable Sandbox()) { 171 import unit_threaded.should; 172 173 shouldNotExist("baz.txt"); 174 writeFile("baz.txt"); 175 shouldNotExist("baz.txt").shouldThrow; 176 } 177 } 178 179 /// read a file in the test sandbox and verify its contents 180 void shouldEqualLines(in string fileName, in string[] lines, 181 string file = __FILE__, size_t line = __LINE__) const @trusted { 182 import std.file; 183 import std.string; 184 import unit_threaded.should; 185 186 readText(buildPath(testPath, fileName)).chomp.splitLines 187 .shouldEqual(lines, file, line); 188 } 189 190 /// 191 @safe unittest { 192 with(immutable Sandbox()) { 193 import unit_threaded.should; 194 195 writeFile("lines.txt", ["foo", "toto"]); 196 shouldEqualLines("lines.txt", ["foo", "bar"]).shouldThrow; 197 shouldEqualLines("lines.txt", ["foo", "toto"]); 198 } 199 } 200 201 private: 202 203 static string newTestDir() { 204 import std.file: exists, mkdirRecurse; 205 206 if(!sandboxPath.exists) { 207 () @trusted { mkdirRecurse(sandboxPath); }(); 208 } 209 210 return makeTempDir(); 211 } 212 213 static string makeTempDir() { 214 import std.algorithm: copy; 215 import std.exception: enforce; 216 import std.conv: to; 217 import core.stdc.string: strerror; 218 import core.stdc.errno: errno; 219 220 char[100] template_; 221 copy(buildPath(sandboxPath, "XXXXXX") ~ '\0', template_[]); 222 223 auto ret = () @trusted { return mkdtemp(&template_[0]).to!string; }(); 224 225 enforce(ret != "", "Failed to create temporary directory name: " ~ 226 () @trusted { return strerror(errno).to!string; }()); 227 228 return ret.absolutePath; 229 } 230 }