1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This module contains tools for testing where a sandbox is needed for creating 7 temporary files. 8 */ 9 module my.test; 10 11 import std.path : buildPath, baseName; 12 import std.format : format; 13 14 import my.path; 15 16 private AbsolutePath tmpDir() { 17 import std.file : thisExePath; 18 import std.path : dirName; 19 20 return buildPath(thisExePath.dirName, "test_area").AbsolutePath; 21 } 22 23 TestArea makeTestArea(string name, string file = __FILE__) { 24 return TestArea(buildPath(file.baseName, name)); 25 } 26 27 struct TestArea { 28 import std.file : rmdirRecurse, mkdirRecurse, exists, readText; 29 import std.process : wait; 30 import std.stdio : File, stdin; 31 static import std.process; 32 33 const AbsolutePath sandboxPath; 34 private int commandLogCnt; 35 36 this(string name) { 37 sandboxPath = buildPath(tmpDir, name).AbsolutePath; 38 39 if (exists(sandboxPath)) { 40 rmdirRecurse(sandboxPath); 41 } 42 mkdirRecurse(sandboxPath); 43 } 44 45 /// Execute a command in the sandbox. 46 string exec(Args...)(auto ref Args args_) { 47 string[] args; 48 static foreach (a; args_) 49 args ~= a; 50 51 const log = inSandbox(format!"command%s.log"(commandLogCnt++).Path); 52 53 try { 54 auto fout = File(log, "w"); 55 fout.writefln("%-(%s %)", args); 56 57 auto exitCode = std.process.spawnProcess(args, stdin, fout, fout, 58 env, std.process.Config.none, sandboxPath).wait; 59 fout.writeln("exit code: ", exitCode); 60 } catch (Exception e) { 61 } 62 return readText(log); 63 } 64 65 string exec(string[] args, string[string] env) { 66 const log = inSandbox(format!"command%s.log"(commandLogCnt++).Path); 67 68 try { 69 auto fout = File(log, "w"); 70 fout.writefln("%-(%s %)", args); 71 72 auto exitCode = std.process.spawnProcess(args, stdin, fout, fout, 73 env, std.process.Config.none, sandboxPath).wait; 74 fout.writeln("exit code: ", exitCode); 75 } catch (Exception e) { 76 } 77 return readText(log); 78 } 79 80 Path inSandbox(string fileName) @safe pure nothrow const { 81 return sandboxPath ~ fileName; 82 } 83 } 84 85 void dirContentCopy(Path src, Path dst) { 86 import std.algorithm; 87 import std.file; 88 import std.path; 89 import my.file; 90 91 assert(src.isDir); 92 assert(dst.isDir); 93 94 foreach (f; dirEntries(src, SpanMode.shallow).filter!"a.isFile") { 95 auto dst_f = buildPath(dst, f.name.baseName).Path; 96 copy(f.name, dst_f); 97 if (isExecutable(Path(f.name))) 98 setExecutable(dst_f); 99 } 100 } 101 102 auto regexIn(T)(string rawRegex, T[] array, string file = __FILE__, in size_t line = __LINE__) { 103 import std.regex : regex, matchFirst; 104 105 auto r = regex(rawRegex); 106 107 foreach (v; array) { 108 if (!matchFirst(v, r).empty) 109 return; 110 } 111 112 import unit_threaded.exception : fail; 113 114 fail(formatValueInItsOwnLine("Value ", 115 rawRegex) ~ formatValueInItsOwnLine("not in ", array), file, line); 116 } 117 118 auto regexNotIn(T)(string rawRegex, T[] array, string file = __FILE__, in size_t line = __LINE__) { 119 import std.regex : regex, matchFirst; 120 import unit_threaded.exception : fail; 121 122 auto r = regex(rawRegex); 123 124 foreach (v; array) { 125 if (!matchFirst(v, r).empty) { 126 fail(formatValueInItsOwnLine("Value ", 127 rawRegex) ~ formatValueInItsOwnLine("in ", array), file, line); 128 return; 129 } 130 } 131 } 132 133 string[] formatValueInItsOwnLine(T)(in string prefix, scope auto ref T value) { 134 import std.conv : to; 135 import std.traits : isSomeString; 136 import std.range.primitives : isInputRange; 137 import std.traits; // too many to list 138 import std.range; // also 139 140 static if (isSomeString!T) { 141 // isSomeString is true for wstring and dstring, 142 // so call .to!string anyway 143 return [prefix ~ `"` ~ value.to!string ~ `"`]; 144 } else static if (isInputRange!T) { 145 return formatRange(prefix, value); 146 } else { 147 return [prefix ~ convertToString(value)]; 148 } 149 } 150 151 string[] formatRange(T)(in string prefix, scope auto ref T value) { 152 import std.conv : text; 153 import std.range : ElementType; 154 import std.algorithm : map, reduce, max; 155 156 //some versions of `text` are @system 157 auto defaultLines = () @trusted { return [prefix ~ value.text]; }(); 158 159 static if (!isInputRange!(ElementType!T)) 160 return defaultLines; 161 else { 162 import std.array : array; 163 164 const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length) 165 .reduce!max; 166 const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) 167 || maxElementSize > 10; 168 if (!tooBigForOneLine) 169 return defaultLines; 170 return [prefix ~ "["] ~ value.map!(a => formatValueInItsOwnLine(" ", 171 a).join("") ~ ",").array ~ " ]"; 172 } 173 }