1#include "test.h"
2
3#include <base/dbg.h>
4#include <base/fs.h>
5#include <base/logger.h>
6#include <base/net.h>
7#include <base/os.h>
8#include <base/process.h>
9#include <base/str.h>
10
11#include <engine/storage.h>
12
13#include <gtest/gtest.h>
14
15#include <algorithm>
16#include <mutex>
17
18CTestInfo::CTestInfo()
19{
20 const ::testing::TestInfo *pTestInfo =
21 ::testing::UnitTest::GetInstance()->current_test_info();
22
23 // Typed tests have test names like "TestName/0" and "TestName/1", which would result in invalid filenames.
24 // Replace the string after the first slash with the name of the typed test and use hyphen instead of slash.
25 char aTestCaseName[128];
26 str_copy(dst&: aTestCaseName, src: pTestInfo->test_case_name());
27 for(int i = 0; i < str_length(str: aTestCaseName); i++)
28 {
29 if(aTestCaseName[i] == '/')
30 {
31 aTestCaseName[i] = '-';
32 aTestCaseName[i + 1] = '\0';
33 str_append(dst&: aTestCaseName, src: pTestInfo->type_param());
34 break;
35 }
36 }
37 str_format(buffer: m_aFilenamePrefix, buffer_size: sizeof(m_aFilenamePrefix), format: "%s.%s-%d",
38 aTestCaseName, pTestInfo->name(), process_id());
39 Filename(pBuffer: m_aFilename, BufferLength: sizeof(m_aFilename), pSuffix: ".tmp");
40}
41
42void CTestInfo::Filename(char *pBuffer, size_t BufferLength, const char *pSuffix)
43{
44 str_format(buffer: pBuffer, buffer_size: BufferLength, format: "%s%s", m_aFilenamePrefix, pSuffix);
45}
46
47std::unique_ptr<IStorage> CTestInfo::CreateTestStorage()
48{
49 bool Error = fs_makedir(path: m_aFilename);
50 EXPECT_FALSE(Error);
51 if(Error)
52 {
53 return nullptr;
54 }
55 char aTestPath[IO_MAX_PATH_LENGTH];
56 str_copy(dst&: aTestPath, src: ::testing::internal::GetArgvs().front().c_str());
57 const char *apArgs[] = {aTestPath};
58 return CreateTempStorage(pDirectory: m_aFilename, NumArgs: std::size(apArgs), ppArguments: apArgs);
59}
60
61class CTestInfoPath
62{
63public:
64 bool m_IsDirectory;
65 char m_aData[IO_MAX_PATH_LENGTH];
66
67 bool operator<(const CTestInfoPath &Other) const
68 {
69 if(m_IsDirectory != Other.m_IsDirectory)
70 {
71 return m_IsDirectory < Other.m_IsDirectory;
72 }
73 return str_comp(a: m_aData, b: Other.m_aData) > 0;
74 }
75};
76
77class CTestCollectData
78{
79public:
80 char m_aCurrentDir[IO_MAX_PATH_LENGTH];
81 std::vector<CTestInfoPath> *m_pvEntries;
82};
83
84int TestCollect(const char *pName, int IsDir, int Unused, void *pUser)
85{
86 CTestCollectData *pData = (CTestCollectData *)pUser;
87
88 if(str_comp(a: pName, b: ".") == 0 || str_comp(a: pName, b: "..") == 0)
89 {
90 return 0;
91 }
92
93 CTestInfoPath Path;
94 Path.m_IsDirectory = IsDir;
95 str_format(buffer: Path.m_aData, buffer_size: sizeof(Path.m_aData), format: "%s/%s", pData->m_aCurrentDir, pName);
96 pData->m_pvEntries->push_back(x: Path);
97 if(Path.m_IsDirectory)
98 {
99 CTestCollectData DataRecursive;
100 str_copy(dst: DataRecursive.m_aCurrentDir, src: Path.m_aData, dst_size: sizeof(DataRecursive.m_aCurrentDir));
101 DataRecursive.m_pvEntries = pData->m_pvEntries;
102 fs_listdir(dir: DataRecursive.m_aCurrentDir, cb: TestCollect, type: 0, user: &DataRecursive);
103 }
104 return 0;
105}
106
107void TestDeleteTestStorageFiles(const char *pPath)
108{
109 std::vector<CTestInfoPath> vEntries;
110 CTestCollectData Data;
111 str_copy(dst: Data.m_aCurrentDir, src: pPath, dst_size: sizeof(Data.m_aCurrentDir));
112 Data.m_pvEntries = &vEntries;
113 fs_listdir(dir: Data.m_aCurrentDir, cb: TestCollect, type: 0, user: &Data);
114
115 CTestInfoPath Path;
116 Path.m_IsDirectory = true;
117 str_copy(dst: Path.m_aData, src: Data.m_aCurrentDir, dst_size: sizeof(Path.m_aData));
118 vEntries.push_back(x: Path);
119
120 // Sorts directories after files.
121 std::sort(first: vEntries.begin(), last: vEntries.end());
122
123 // Don't delete too many files.
124 ASSERT_LE(vEntries.size(), 10);
125 for(auto &Entry : vEntries)
126 {
127 if(Entry.m_IsDirectory)
128 {
129 ASSERT_FALSE(fs_removedir(Entry.m_aData));
130 }
131 else
132 {
133 ASSERT_FALSE(fs_remove(Entry.m_aData));
134 }
135 }
136}
137
138CTestInfo::~CTestInfo()
139{
140 if(!::testing::Test::HasFailure() && m_DeleteTestStorageFilesOnSuccess)
141 {
142 TestDeleteTestStorageFiles(pPath: m_aFilename);
143 }
144}
145
146class CTestLogger : public ILogger
147{
148 friend class CTestLogListener;
149
150 std::unique_ptr<ILogger> m_pGlobalLogger;
151 std::unique_ptr<CMemoryLogger> m_pMemoryLogger;
152 std::mutex m_Lock;
153
154public:
155 CTestLogger(std::unique_ptr<ILogger> &&pGlobalLogger) :
156 m_pGlobalLogger(std::move(pGlobalLogger))
157 {
158 }
159
160 void Log(const CLogMessage *pMessage) override
161 {
162 if(m_Filter.Filters(pMessage))
163 {
164 return;
165 }
166 const std::unique_lock Lock(m_Lock);
167 if(m_pMemoryLogger != nullptr)
168 {
169 m_pMemoryLogger->Log(pMessage);
170 }
171 else
172 {
173 m_pGlobalLogger->Log(pMessage);
174 }
175 }
176
177 void GlobalFinish() override
178 {
179 dbg_assert(m_pMemoryLogger == nullptr, "Memory logger should be unset when test logger finishes");
180 m_pGlobalLogger->GlobalFinish();
181 }
182};
183
184class CTestLogListener : public testing::EmptyTestEventListener
185{
186 CTestLogger *m_pTestLogger;
187 bool m_CaptureOutput;
188
189public:
190 CTestLogListener(CTestLogger *pTestLogger, bool CaptureOutput) :
191 m_pTestLogger(pTestLogger),
192 m_CaptureOutput(CaptureOutput)
193 {
194 }
195
196 void OnTestStart(const testing::TestInfo &TestInfo) override
197 {
198 if(!m_CaptureOutput)
199 {
200 return;
201 }
202
203 const std::unique_lock Lock(m_pTestLogger->m_Lock);
204 m_pTestLogger->m_pMemoryLogger = std::make_unique<CMemoryLogger>();
205 }
206
207 void OnTestEnd(const testing::TestInfo &TestInfo) override
208 {
209 if(!m_CaptureOutput)
210 {
211 return;
212 }
213
214 const std::unique_lock Lock(m_pTestLogger->m_Lock);
215 if(TestInfo.result()->Failed())
216 {
217 for(const CLogMessage &Line : m_pTestLogger->m_pMemoryLogger->Lines())
218 {
219 m_pTestLogger->m_pGlobalLogger->Log(pMessage: &Line);
220 }
221 }
222 m_pTestLogger->m_pMemoryLogger = nullptr;
223 }
224};
225
226int main(int argc, const char **argv)
227{
228 CTestLogger *pTestLogger = std::make_unique<CTestLogger>(args: log_logger_default()).release();
229 log_set_global_logger(logger: pTestLogger);
230
231 CCmdlineFix CmdlineFix(&argc, &argv);
232
233 ::testing::InitGoogleTest(argc: &argc, argv: const_cast<char **>(argv));
234 GTEST_FLAG_SET(death_test_style, "threadsafe");
235
236 bool CaptureOutput = true;
237 if(argc >= 2)
238 {
239 for(int Argument = 1; Argument < argc; ++Argument)
240 {
241 if(str_comp(a: argv[Argument], b: "--no-capture") == 0)
242 {
243 CaptureOutput = false;
244 }
245 else
246 {
247 log_error("testrunner", "Unknown argument: %s", argv[Argument]);
248 return -1;
249 }
250 }
251 }
252
253 testing::TestEventListeners &Listeners = testing::UnitTest::GetInstance()->listeners();
254 Listeners.Append(listener: new CTestLogListener(pTestLogger, CaptureOutput));
255
256 net_init();
257
258 return RUN_ALL_TESTS();
259}
260
261TEST(TestInfo, Sort)
262{
263 std::vector<CTestInfoPath> vEntries;
264 vEntries.resize(sz: 3);
265
266 const char aBasePath[] = "test_dir";
267 const char aSubPath[] = "test_dir/subdir";
268 const char aFilePath[] = "test_dir/subdir/file.txt";
269
270 vEntries[0].m_IsDirectory = true;
271 str_copy(dst&: vEntries[0].m_aData, src: aBasePath);
272
273 vEntries[1].m_IsDirectory = true;
274 str_copy(dst&: vEntries[1].m_aData, src: aSubPath);
275
276 vEntries[2].m_IsDirectory = false;
277 str_copy(dst&: vEntries[2].m_aData, src: aFilePath);
278
279 // Sorts directories after files.
280 std::sort(first: vEntries.begin(), last: vEntries.end());
281
282 EXPECT_FALSE(vEntries[0].m_IsDirectory);
283 EXPECT_EQ(str_comp(vEntries[0].m_aData, aFilePath), 0);
284 EXPECT_TRUE(vEntries[1].m_IsDirectory);
285 EXPECT_EQ(str_comp(vEntries[1].m_aData, aSubPath), 0);
286 EXPECT_TRUE(vEntries[2].m_IsDirectory);
287 EXPECT_EQ(str_comp(vEntries[2].m_aData, aBasePath), 0);
288}
289