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