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  }