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; 100 import std.path; 101 File(buildPath(testPath, fileName), "w").writeln(output); 102 } 103 104 /// Write a file to the sanbox 105 void writeFile(in string fileName, in string[] lines) const { 106 import std.array; 107 writeFile(fileName, lines.join("\n")); 108 } 109 110 /// 111 @safe unittest { 112 import std.file; 113 import std.path; 114 115 with(immutable Sandbox()) { 116 assert(!buildPath(testPath, "foo.txt").exists); 117 writeFile("foo.txt"); 118 assert(buildPath(testPath, "foo.txt").exists); 119 } 120 } 121 122 /// Assert that a file exists in the sandbox 123 void shouldExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 124 import std.file; 125 import std.path; 126 import unit_threaded.should: fail; 127 128 fileName = buildPath(testPath, fileName); 129 if(!fileName.exists) 130 fail("Expected " ~ fileName ~ " to exist but it didn't", file, line); 131 } 132 133 /// 134 @safe unittest { 135 with(immutable Sandbox()) { 136 import unit_threaded.should; 137 138 shouldExist("bar.txt").shouldThrow; 139 writeFile("bar.txt"); 140 shouldExist("bar.txt"); 141 } 142 } 143 144 /// Assert that a file does not exist in the sandbox 145 void shouldNotExist(string fileName, in string file = __FILE__, in size_t line = __LINE__) const { 146 import std.file; 147 import std.path; 148 import unit_threaded.should; 149 150 fileName = buildPath(testPath, fileName); 151 if(fileName.exists) 152 fail("Expected " ~ fileName ~ " to not exist but it did", file, line); 153 } 154 155 /// 156 @safe unittest { 157 with(immutable Sandbox()) { 158 import unit_threaded.should; 159 160 shouldNotExist("baz.txt"); 161 writeFile("baz.txt"); 162 shouldNotExist("baz.txt").shouldThrow; 163 } 164 } 165 166 /// read a file in the test sandbox and verify its contents 167 void shouldEqualLines(in string fileName, in string[] lines, 168 string file = __FILE__, size_t line = __LINE__) const @trusted { 169 import std.file; 170 import std..string; 171 import unit_threaded.should; 172 173 readText(buildPath(testPath, fileName)).chomp.splitLines 174 .shouldEqual(lines, file, line); 175 } 176 177 /// 178 @safe unittest { 179 with(immutable Sandbox()) { 180 import unit_threaded.should; 181 182 writeFile("lines.txt", ["foo", "toto"]); 183 shouldEqualLines("lines.txt", ["foo", "bar"]).shouldThrow; 184 shouldEqualLines("lines.txt", ["foo", "toto"]); 185 } 186 } 187 188 private: 189 190 static string newTestDir() { 191 import std.file: exists, mkdirRecurse; 192 193 if(!sandboxPath.exists) { 194 () @trusted { mkdirRecurse(sandboxPath); }(); 195 } 196 197 return makeTempDir(); 198 } 199 200 static string makeTempDir() { 201 import std.algorithm: copy; 202 import std.exception: enforce; 203 import std.conv: to; 204 import core.stdc..string: strerror; 205 import core.stdc.errno: errno; 206 207 char[100] template_; 208 copy(buildPath(sandboxPath, "XXXXXX") ~ '\0', template_[]); 209 210 auto ret = () @trusted { return mkdtemp(&template_[0]).to!string; }(); 211 212 enforce(ret != "", "Failed to create temporary directory name: " ~ 213 () @trusted { return strerror(errno).to!string; }()); 214 215 return ret.absolutePath; 216 } 217 }