MiKTeX 26.5-next
A scalable TeX distribution
Loading...
Searching...
No Matches
Test.h
1/* miktex/Core/test.h: test framework -*- C++ -*-
2
3 Copyright (C) 1996-2024 Christian Schenk
4
5 This file is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 2, or (at your
8 option) any later version.
9
10 This file is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this file; if not, write to the Free Software
17 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 USA. */
19
20#pragma once
21
22#if !defined(F3C19AE97A4B49AC9E96B6A8293DC1A7)
23#define F3C19AE97A4B49AC9E96B6A8293DC1A7
24
25#include <cstdio>
26
27#include <exception>
28#include <memory>
29#include <string>
30#include <vector>
31
32#if defined(_MSC_VER)
33# include <direct.h>
34# include <io.h>
35# include <fcntl.h>
36# include <process.h>
37#endif
38
39#include <miktex/Core/Exceptions>
40#include <miktex/Util/PathName>
41#include <miktex/Core/Process>
42#include <miktex/Core/Session>
43#include <miktex/Trace/Trace>
44#include <miktex/Trace/TraceCallback>
45#include <miktex/Trace/TraceStream>
46#include <miktex/Wrappers/PoptWrapper>
47
48#include <log4cxx/basicconfigurator.h>
49#include <log4cxx/logger.h>
50#include <log4cxx/xml/domconfigurator.h>
51
52#define MIKTEXTEST_BEGIN_NAMESPACE \
53 namespace MiKTeX { \
54 namespace Test {
55
56#define MIKTEXTEST_END_NAMESPACE \
57 } \
58 }
59
60MIKTEXTEST_BEGIN_NAMESPACE
61
62#define T_(x) MIKTEXTEXT(x)
63
64#if defined(MIKTEX_WINDOWS)
65const char PATH_DELIMITER = ';';
66#define PATH_DELIMITER_STRING ";"
67#else
68const char PATH_DELIMITER = ':';
69#define PATH_DELIMITER_STRING ":"
70#endif
71
72class TestException :
73 public MiKTeX::Core::MiKTeXException
74{
75public:
76 TestException(const std::string& programInvocationName, const std::string& errorMessage, const std::string& description, const std::string& remedy, const std::string& tag, const KVMAP& info, const MiKTeX::Core::SourceLocation& sourceLocation) :
77 MiKTeXException(programInvocationName, errorMessage, description, remedy, tag, info, sourceLocation)
78 {
79
80 }
81};
82
83enum class Option
84{
85 RootDirectories = 1000,
86 InstallRoot,
87 DataRoot,
88 Trace,
89};
90
91#define FATAL() \
92 LOG4CXX_FATAL(logger, "fatal error in " << __FILE__ << ", line " << __LINE__); \
93 throw 1
94
95class TestScript : public MiKTeX::Trace::TraceCallback
96{
97protected:
98 TestScript(std::string name) :
99 scriptName(name)
100 {
101#if defined(TRACEFLAGS)
102 traceFlags = TRACEFLAGS;
103#endif
104 }
105
106protected:
107 std::string scriptName;
108
109protected:
110 log4cxx::LoggerPtr logger = nullptr;
111
112private:
113 bool isLog4cxxConfigured = false;
114
115private:
116 std::vector<MiKTeX::Trace::TraceCallback::TraceMessage> pendingTraceMessages;
117
118public:
119 bool MIKTEXTHISCALL Trace(const MiKTeX::Trace::TraceCallback::TraceMessage& traceMessage) override
120 {
121 if (!isLog4cxxConfigured)
122 {
123 if (pendingTraceMessages.size() > 100)
124 {
125 pendingTraceMessages.clear();
126 }
127 pendingTraceMessages.push_back(traceMessage);
128 return true;
129 }
130 FlushPendingTraceMessages();
131 TraceInternal(traceMessage);
132 return true;
133 }
134
135private:
136 void FlushPendingTraceMessages()
137 {
138 for (const MiKTeX::Trace::TraceCallback::TraceMessage& msg : pendingTraceMessages)
139 {
140 TraceInternal(msg);
141 }
142 pendingTraceMessages.clear();
143 }
144
145private:
146 void TraceInternal(const MiKTeX::Trace::TraceCallback::TraceMessage& traceMessage)
147 {
148 log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger(std::string("trace.test.") + traceMessage.facility);
149 switch (traceMessage.level)
150 {
151 case MiKTeX::Trace::TraceLevel::Fatal:
152 LOG4CXX_FATAL(logger, traceMessage.message);
153 break;
154 case MiKTeX::Trace::TraceLevel::Error:
155 LOG4CXX_ERROR(logger, traceMessage.message);
156 break;
157 case MiKTeX::Trace::TraceLevel::Warning:
158 LOG4CXX_WARN(logger, traceMessage.message);
159 break;
160 case MiKTeX::Trace::TraceLevel::Info:
161 LOG4CXX_INFO(logger, traceMessage.message);
162 break;
163 case MiKTeX::Trace::TraceLevel::Trace:
164 LOG4CXX_TRACE(logger, traceMessage.message);
165 break;
166 case MiKTeX::Trace::TraceLevel::Debug:
167 default:
168 LOG4CXX_DEBUG(logger, traceMessage.message);
169 break;
170 }
171 }
172
173public:
174 virtual ~TestScript()
175 {
176 }
177
178protected:
179 std::vector<std::string> argv;
180
181public:
182 int Main(int argc, const char** argv)
183 {
184#if defined(_MSC_VER)
185 _setmode(_fileno(stdin), _O_BINARY);
186 _setmode(_fileno(stdout), _O_BINARY);
187 _setmode(_fileno(stderr), _O_BINARY);
188#endif
189 int rc = 0;
190 int chOption;
191 MiKTeX::Wrappers::PoptWrapper popt(argc, argv, optionTable);
192 while ((chOption = popt.GetNextOpt()) >= 0)
193 {
194 switch ((Option)chOption)
195 {
196 case Option::InstallRoot:
197 installRoot = popt.GetOptArg();
198 break;
199 case Option::DataRoot:
200 dataRoot = popt.GetOptArg();
201 break;
202 case Option::RootDirectories:
203 rootDirectories = popt.GetOptArg();
204 break;
205 case Option::Trace:
206 traceFlags = popt.GetOptArg();
207 break;
208 default:
209 ;
210 }
211 }
212 for (const std::string& arg : popt.GetLeftovers())
213 {
214 vecArgs.push_back(arg);
215 }
216 try
217 {
218 for (int idx = 0; idx < argc && argv[idx] != nullptr; ++idx)
219 {
220 this->argv.push_back(argv[idx]);
221 }
222 MiKTeX::Core::Session::InitInfo initInfo(argv[0]);
223 MiKTeX::Core::StartupConfig startupConfig;
224
225#if defined(INSTALLROOT) && defined(DATAROOT)
226 startupConfig.userDataRoot = DATAROOT;
227 startupConfig.userInstallRoot = INSTALLROOT;
228#else
229 startupConfig.userRoots = rootDirectories;
230 startupConfig.userDataRoot = dataRoot;
231 startupConfig.userInstallRoot = installRoot;
232#endif
233 initInfo.SetStartupConfig(startupConfig);
234
235#if defined(INITFLAGS)
236 initInfo.SetFlags(INITFLAGS);
237#endif
238
239#if defined(BINDIR)
240# error BINDIR is deprecated
241#endif
242
243 initInfo.SetTraceCallback(this);
244
245 pSession = MiKTeX::Core::Session::Create(initInfo);
246
247 MiKTeX::Util::PathName xmlFileName;
248 if (pSession->FindFile("log4cxx.xml", "%R/miktex/config", xmlFileName))
249 {
250#if defined(MIKTEX_WINDOWS)
251 log4cxx::xml::DOMConfigurator::configure(xmlFileName.ToWideCharString());
252#else
253 log4cxx::xml::DOMConfigurator::configure(xmlFileName.ToString());
254#endif
255 }
256 else
257 {
258 log4cxx::BasicConfigurator::configure();
259 }
260 logger = log4cxx::Logger::getLogger(scriptName);
261 isLog4cxxConfigured = true;
262
263 LOG4CXX_INFO(logger, "starting tests");
264 if (!traceFlags.empty())
265 {
266 MiKTeX::Trace::TraceStream::SetOptions(traceFlags);
267 }
268 Run();
269 pSession->Close();
270 pSession = nullptr;
271 }
272 catch (const FAILURE& f)
273 {
274 if (isLog4cxxConfigured)
275 {
276 LOG4CXX_FATAL(logger, f.strFile << ":" << f.iLine << ": TEST FAILED");
277 }
278 rc = 1;
279 }
280 catch (const TestException& e)
281 {
282 if (isLog4cxxConfigured)
283 {
284 LOG4CXX_INFO(logger, "caught TestException");
285 }
286 e.Save();
287 rc = 1;
288 }
289 catch (const MiKTeX::Core::MiKTeXException& e)
290 {
291 fprintf(stderr, "catched MiKTeXException thrown here: %s:%d\n", e.GetSourceFile().c_str(), e.GetSourceLine());
292 if (isLog4cxxConfigured)
293 {
294 LOG4CXX_FATAL(logger, e.what());
295 LOG4CXX_FATAL(logger, "Info: " << e.GetInfo());
296 LOG4CXX_FATAL(logger, "Source: " << e.GetSourceFile());
297 LOG4CXX_FATAL(logger, "Line: " << e.GetSourceLine());
298 }
299 rc = 1;
300 }
301 catch (const std::exception& e)
302 {
303 fprintf(stderr, "catched std::exception: %s\n", e.what());
304 if (isLog4cxxConfigured)
305 {
306 LOG4CXX_FATAL(logger, e.what());
307 }
308 rc = 1;
309 }
310 catch (int code)
311 {
312 fprintf(stderr, "catched int: %d\n", code);
313 if (isLog4cxxConfigured)
314 {
315 LOG4CXX_FATAL(logger, "test script failure " << code);
316 }
317 rc = 1;
318 }
319 return rc;
320 }
321
322protected:
323 struct FAILURE
324 {
325 int iNum;
326 std::string strFile;
327 int iLine;
328 FAILURE(int i) :
329 iNum(i)
330 {
331 }
332 };
333
334protected:
335 virtual void Run() = 0;
336
337protected:
338 bool FindFile(const char* lpszFileName, const char* lpszPathList)
339 {
341 return pSession->FindFile(lpszFileName, lpszPathList, path);
342 }
343
344protected:
345 bool RunTestCommand(const char* lpszProgramName)
346 {
347 std::string commandLine = MiKTeX::Util::PathName(pSession->GetMyLocation(false) / lpszProgramName).ToString();
348 for (std::vector<std::string>::const_iterator it = argv.begin(); it != argv.end(); ++it)
349 {
350 if (it != argv.begin())
351 {
352 commandLine += " " + *it;
353 }
354 }
355 return system(commandLine.c_str()) == 0;
356 }
357
358protected:
359 void Touch(const char* lpszPath)
360 {
361 FILE* pFile;
362#if defined(_MSC_VER) && _MSC_VER >= 1400
363 if (_wfopen_s(&pFile, MiKTeX::Util::PathName(lpszPath).ToExtendedLengthPathName().ToWideCharString().c_str(), L"wb") != 0)
364 {
365 pFile = nullptr;
366 }
367#else
368 pFile = fopen(lpszPath, "wb");
369#endif
370 if (pFile == nullptr)
371 {
372 FATAL();
373 }
374 fclose(pFile);
375 }
376
377protected:
378 void Touch(const MiKTeX::Util::PathName& path)
379 {
380 Touch(path.GetData());
381 }
382
383protected:
384 std::shared_ptr<MiKTeX::Core::Session> pSession;
385
386protected:
387 std::vector<std::string> vecArgs;
388
389protected:
390 std::string traceFlags;
391
392protected:
393 std::string rootDirectories;
394
395protected:
396 std::string installRoot;
397
398protected:
399 std::string dataRoot;
400
401private:
402 static const struct poptOption optionTable[];
403};
404
405const struct poptOption TestScript::optionTable[] = {
406 { "install-root", 0, POPT_ARG_STRING, nullptr, (int)Option::InstallRoot,
407 T_("Set the install root directory."), nullptr },
408 { "data-root", 0, POPT_ARG_STRING, nullptr, (int)Option::DataRoot,
409 T_("Set the data root directory."), nullptr },
410 { "root-directories", 0, POPT_ARG_STRING, nullptr, (int)Option::RootDirectories,
411 T_("Set the root directories."), nullptr },
412 { "trace", 0, POPT_ARG_STRING, nullptr, (int)Option::Trace,
413 T_("Set trace flags."), nullptr },
414 POPT_TABLEEND
415};
416
417#define FAIL() f.iLine = __LINE__; f.strFile = __FILE__; throw f
418
419#define TEST(exp) \
420 LOG4CXX_INFO(logger, "Test: " << #exp); \
421 if (! (exp)) { FAIL(); }
422
423#define TESTX(exp) \
424 LOG4CXX_INFO(logger, "Test: " << #exp); \
425 try \
426 { \
427 exp; \
428 } \
429 catch (const MiKTeX::Core::MiKTeXException&) \
430 { \
431 FAIL(); \
432 }
433
434#define BEGIN_TEST_FUNCTION(num) \
435 void TestFunc##num() \
436 { \
437 FAILURE f(num); \
438 LOG4CXX_INFO(logger, "Testfunc " << num) \
439
440#define END_TEST_FUNCTION() \
441 }
442
443#define CALL_TEST_FUNCTION(num) TestFunc##num ()
444
445#define BEGIN_TEST_SCRIPT(name) \
446 class MyTestScript : public TestScript \
447 { \
448 public: \
449 MyTestScript() : TestScript(name) {} \
450 protected:
451
452#define END_TEST_SCRIPT() \
453 };
454
455#define BEGIN_TEST_PROGRAM() \
456protected: \
457 virtual void Run() \
458 {
459
460#define END_TEST_PROGRAM() \
461 }
462
463#define RUN_TEST_SCRIPT() \
464int main (int argc, const char **argv) \
465{ \
466 MyTestScript script; \
467 return script.Main(argc, argv); \
468}
469
470MIKTEXTEST_END_NAMESPACE;
471
472#endif
Extended initialization options.
Definition Session.h:436
void SetTraceCallback(MiKTeX::Trace::TraceCallback *callback)
Definition Session.h:540
void SetStartupConfig(const StartupConfig &startupConfig)
Definition Session.h:512
@ Create
Try to create the file, if it doesn't exist.
Definition Session.h:409
Definition TraceCallback.h:45
std::basic_string< CharType > ToString() const
Definition CharBuffer.h:242
Instances of this class can be used to store path names.
Definition PathName.h:64
Definition Test.h:74
Definition Test.h:96
@ ToExtendedLengthPathName
Prefix with \?\ to create an extended-length path name.
Definition PathName.h:42
Definition Exceptions.h:38
Startup configuration.
Definition Session.h:124
MiKTeX::Util::PathName userInstallRoot
User installation root directory.
Definition Session.h:132
MiKTeX::Util::PathName userDataRoot
User data root directory.
Definition Session.h:129
std::string userRoots
Additional TEXMF roots defined by the user.
Definition Session.h:135
Definition TraceCallback.h:48
Definition Test.h:324