1 /** 2 Helper functions for tsv-utils unit tests. 3 4 Copyright (c) 2017-2021, eBay Inc. 5 Initially written by Jon Degenhardt 6 7 License: Boost License 1.0 (http://boost.org/LICENSE_1_0.txt) 8 */ 9 10 module tsv_utils.common.unittest_utils; 11 12 version(unittest) 13 { 14 /* Creates a temporary directory for writing unit test files. The path of the created 15 * directory is returned. The 'toolDirName' argument will be included in the directory 16 * name, and should consist of generic filename characters. e.g. "tsv_append". This 17 * name will also be used in assert error messages. 18 * 19 * The caller should delete the temporary directory and all its contents when tests 20 * are finished. This can be done using std.file.rmdirRecurse. For example: 21 * 22 * unittest 23 * { 24 * import std.file : rmdirRecurse; 25 * auto testDir = makeUnittestTempDir("tsv_append"); 26 * scope(exit) testDir.rmdirRecurse; 27 * ... test code 28 * } 29 * 30 * An assert is triggered if the directory cannot be created. There are two typical 31 * reasons: 32 * - Unable to find an available directory name. A number of unique names are tried 33 * (currently 1000). If they are all taken, it will normally be because the directories 34 * haven't been properly cleaned up from previous unit test runs. 35 * - Directory creation failed. e.g. Permission denied. 36 * 37 * This routine is intended to be run in 'unittest' mode, so that an assert is triggered 38 * on failure. However, if run with asserts disabled, the returned path will be empty in 39 * event of a failure. 40 */ 41 string makeUnittestTempDir(string toolDirName) @safe 42 { 43 import std.conv : to; 44 import std.file : exists, mkdir, tempDir; 45 import std.format : format; 46 import std.path : buildPath; 47 import std.range; 48 49 string dirNamePrefix = "ebay_tsv_utils__" ~ toolDirName ~ "_unittest_"; 50 string systemTempDirPath = tempDir(); 51 string newTempDirPath = ""; 52 53 for (auto i = 0; i < 1000 && newTempDirPath.empty; i++) 54 { 55 string path = buildPath(systemTempDirPath, dirNamePrefix ~ i.to!string); 56 if (!path.exists) newTempDirPath = path; 57 } 58 assert (!newTempDirPath.empty, 59 format("Unable to obtain a new temp directory, paths tried already exist.\nPath prefix: %s", 60 buildPath(systemTempDirPath, dirNamePrefix))); 61 62 if (!newTempDirPath.empty) 63 { 64 try mkdir(newTempDirPath); 65 catch (Exception exc) 66 { 67 assert(false, format("Failed to create temp directory: %s\n Error: %s", 68 newTempDirPath, exc.msg)); 69 } 70 } 71 72 return newTempDirPath; 73 } 74 75 /* Write a TSV file. The 'tsvData' argument is a 2-dimensional array of rows and 76 * columns. Asserts if the file cannot be written. 77 * 78 * This routine is intended to be run in 'unittest' mode, so that it will assert 79 * if the write fails. However, if run in a mode with asserts disabled, it will 80 * return false if the write failed. 81 */ 82 bool writeUnittestTsvFile(string filepath, string[][] tsvData, char delimiter = '\t') @safe 83 { 84 import std.algorithm : each, joiner, map; 85 import std.conv : to; 86 import std.format: format; 87 import std.stdio : File; 88 89 try 90 { 91 auto file = File(filepath, "wb"); 92 tsvData 93 .map!(row => row.joiner(delimiter.to!string)) 94 .each!(str => file.writeln(str)); 95 file.close; 96 } 97 catch (Exception exc) 98 { 99 assert(false, format("Failed to write TSV file: %s.\n Error: %s", 100 filepath, exc.msg)); 101 return false; 102 } 103 104 return true; 105 } 106 107 /* Convert a 2-dimensional array of values to an in-memory string. */ 108 string tsvDataToString(string[][] tsvData, char delimiter = '\t') @safe 109 { 110 import std.algorithm : joiner, map; 111 import std.conv : to; 112 113 return tsvData 114 .map!(row => row.joiner(delimiter.to!string).to!string ~ "\n") 115 .joiner 116 .to!string; 117 } 118 }