Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qxctestlogger.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qxctestlogger_p.h"
5
6#include <QtCore/qstring.h>
7
8#include <QtTest/private/qtestlog_p.h>
9#include <QtTest/private/qtestresult_p.h>
10
11#import <XCTest/XCTest.h>
12
13// This XCode logging integration has probably bit-rotted since it was written.
14// It is not even compiled as part of normal builds.
15
16// ---------------------------------------------------------
17
18@interface XCTestProbe (Private)
19+ (BOOL)isTesting;
20+ (void)runTests:(id)unusedArgument;
21+ (NSString*)testScope;
22+ (BOOL)isInverseTestScope;
23@end
24
25@interface XCTestDriver : NSObject
26+ (XCTestDriver*)sharedTestDriver;
27@property (readonly, assign) NSObject *IDEConnection;
28@end
29
30@interface XCTest (Private)
31- (NSString *)nameForLegacyLogging;
32@end
33
35// Ignore XCTestProbe deprecation
37
38// ---------------------------------------------------------
39
40@interface QtTestLibWrapper : XCTestCase
41@end
42
43@interface QtTestLibTests : XCTestSuite
44+ (XCTestSuiteRun*)testRun;
45@end
46
47@interface QtTestLibTest : XCTestCase
48@property (nonatomic, retain) NSString* testObjectName;
49@property (nonatomic, retain) NSString* testFunctionName;
50@end
51
52// ---------------------------------------------------------
53
55{
56public:
65
67 {
68 static ThreadBarriers instance;
69 return &instance;
70 }
71
72 static void initialize() { get(); }
73
74 void wait(Barrier barrier) { dispatch_semaphore_wait(barriers[barrier], DISPATCH_TIME_FOREVER); }
75 void signal(Barrier barrier) { dispatch_semaphore_signal(barriers[barrier]); }
76
77private:
78 #define FOREACH_BARRIER(cmd) for (int i = 0; i < BarrierCount; ++i) { cmd }
79
80 ThreadBarriers() { FOREACH_BARRIER(barriers[i] = dispatch_semaphore_create(0);) }
81 ~ThreadBarriers() { FOREACH_BARRIER(dispatch_release(barriers[i]);) }
82
83 dispatch_semaphore_t barriers[BarrierCount];
84};
85
86#define WAIT_FOR_BARRIER(b) ThreadBarriers::get()->wait(ThreadBarriers::b);
87#define SIGNAL_BARRIER(b) ThreadBarriers::get()->signal(ThreadBarriers::b);
88
89// ---------------------------------------------------------
90
91@implementation QtTestLibWrapper
92
93+ (void)load
94{
95 NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
96
97 if (![XCTestProbe isTesting])
98 return;
99
100 if (Q_UNLIKELY(!([NSDate timeIntervalSinceReferenceDate] > 0)))
101 qFatal("error: Device date '%s' is bad, likely set to update automatically. Please correct.",
102 [[NSDate date] description].UTF8String);
103
104 XCTestDriver *testDriver = nil;
105 if ([QtTestLibWrapper usingTestManager])
106 testDriver = [XCTestDriver sharedTestDriver];
107
108 // Spawn off task to run test infrastructure on separate thread so that we can
109 // let main() execute like normal on the main thread. The queue will never be
110 // destroyed, so there's no point in trying to keep a proper retain count.
111 dispatch_async(dispatch_queue_create("io.qt.QTestLib.xctest-wrapper", DISPATCH_QUEUE_SERIAL), ^{
112 Q_ASSERT(![NSThread isMainThread]);
113 [XCTestProbe runTests:nil];
114 Q_UNREACHABLE();
115 });
116
117 // Initialize barriers before registering exit handler so that the
118 // semaphores stay alive until after the exit handler completes.
120
121 // We register an exit handler so that we can intercept when main() completes
122 // and let the XCTest thread finish up. For main() functions that never started
123 // testing using QtTestLib we also need to signal that xcTestsCanStart.
124 atexit_b(^{
125 Q_ASSERT([NSThread isMainThread]);
126
127 // In case not started by startLogging
128 SIGNAL_BARRIER(XCTestCanStartTesting);
129
130 // [XCTestProbe runTests:] ends up calling [XCTestProbe exitTests:] after
131 // all test suites have completed, which calls exit(). We use that to signal
132 // to the main thread that it's free to continue its exit handler.
133 atexit_b(^{
134 Q_ASSERT(![NSThread isMainThread]);
135 SIGNAL_BARRIER(XCTestsHaveCompleted);
136
137 // Block forever so that the main thread does all the cleanup
138 dispatch_semaphore_wait(dispatch_semaphore_create(0), DISPATCH_TIME_FOREVER);
139 });
140
141 SIGNAL_BARRIER(QtTestsHaveCompleted);
142
143 // Ensure XCTest complets the top level tests suite
144 WAIT_FOR_BARRIER(XCTestsHaveCompleted);
145 });
146
147 // Let test driver (Xcode) connection setup complete before continuing
148 if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--use-testmanagerd"]) {
149 while (!testDriver.IDEConnection)
150 [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
151 }
152
153 // Wait for our QtTestLib test suite to run before running main
154 WAIT_FOR_BARRIER(QtTestsCanStartTesting);
155
156 // Prevent XCTestProbe from re-launching runTests on application startup
157 [[NSNotificationCenter defaultCenter] removeObserver:[XCTestProbe class]
158 name:[NSString stringWithFormat:@"%@DidFinishLaunchingNotification",
159 #if defined(Q_OS_MACOS)
160 @"NSApplication"
161 #else
162 @"UIApplication"
163 #endif
164 ]
165 object:nil];
166
167 [autoreleasepool release];
168}
169
170+ (QTestLibTests *)defaultTestSuite
171{
172 return [[QtTestLibTests alloc] initWithName:@"QtTestLib"];
173}
174
175+ (BOOL)usingTestManager
176{
177 return [[[NSProcessInfo processInfo] arguments] containsObject:@"--use-testmanagerd"];
178}
179
180@end
181
182// ---------------------------------------------------------
183
184static XCTestSuiteRun *s_qtTestSuiteRun = nullptr;
185
186@implementation QtTestLibTests
187
188- (void)performTest:(XCTestSuiteRun *)testSuiteRun
189{
190 Q_ASSERT(![NSThread isMainThread]);
191
193 s_qtTestSuiteRun = testSuiteRun;
194
195 SIGNAL_BARRIER(QtTestsCanStartTesting);
196
197 // Wait for main() to complete, or a QtTestLib test to start, so we
198 // know if we should start the QtTestLib test suite.
199 WAIT_FOR_BARRIER(XCTestCanStartTesting);
200
202 [testSuiteRun start];
203
204 SIGNAL_BARRIER(XCTestHaveStarted);
205
206 // All test reporting happens on main thread from now on. Wait until
207 // main() completes before allowing the XCTest thread to continue.
208 WAIT_FOR_BARRIER(QtTestsHaveCompleted);
209
210 if ([testSuiteRun startDate])
211 [testSuiteRun stop];
212}
213
214+ (XCTestSuiteRun*)testRun
215{
216 return s_qtTestSuiteRun;
217}
218
219@end
220
221// ---------------------------------------------------------
222
223@implementation QtTestLibTest
224
225- (instancetype)initWithInvocation:(NSInvocation *)invocation
226{
227 if (self = [super initWithInvocation:invocation]) {
228 // The test object name and function name are used by XCTest after QtTestLib has
229 // reset them, so we need to store them up front for each XCTestCase.
230 self.testObjectName = [NSString stringWithUTF8String:QTestResult::currentTestObjectName()];
231 self.testFunctionName = [NSString stringWithUTF8String:QTestResult::currentTestFunction()];
232 }
233
234 return self;
235}
236
237- (NSString *)testClassName
238{
239 return self.testObjectName;
240}
241
242- (NSString *)testMethodName
243{
244 return self.testFunctionName;
245}
246
247- (NSString *)nameForLegacyLogging
248{
249 NSString *name = [NSString stringWithFormat:@"%@::%@", [self testClassName], [self testMethodName]];
252 const char *globalDataTag = QTestResult::currentGlobalDataTag() ? QTestResult::currentGlobalDataTag() : "";
253 const char *filler = (currentDataTag[0] && globalDataTag[0]) ? ":" : "";
254 name = [name stringByAppendingString:[NSString stringWithFormat:@"(%s%s%s)",
255 globalDataTag, filler, currentDataTag]];
256 }
257
258 return name;
259}
260
261@end
262
263// ---------------------------------------------------------
264
266{
267 return [XCTestProbe isTesting]; // FIXME: Exclude xctool
268}
269
271{
272 if (strncmp(argument, "-NS", 3) == 0 || strncmp(argument, "-Apple", 6) == 0)
273 return 2; // -NSTreatUnknownArgumentsAsOpen, -ApplePersistenceIgnoreState, etc, skip argument
274 else if (strcmp(argument, "--use-testmanagerd") == 0)
275 return 2; // Skip UID argument
276 else if (strncmp(argument, "-XCTest", 7) == 0)
277 return 2; // -XCTestInvertScope, -XCTest scope, etc, skip argument
278 else if (strcmp(argument + (strlen(argument) - 7), ".xctest") == 0)
279 return 1; // Skip test bundle
280 else
281 return 0;
282}
283
284// ---------------------------------------------------------
285
286QXcodeTestLogger *QXcodeTestLogger::s_currentTestLogger = 0;
287
288// ---------------------------------------------------------
289
292 , m_testRuns([[NSMutableArray<XCTestRun *> arrayWithCapacity:2] retain])
293
294{
295 Q_ASSERT(!s_currentTestLogger);
296 s_currentTestLogger = this;
297}
298
300{
301 s_currentTestLogger = 0;
302 [m_testRuns release];
303}
304
306{
307 SIGNAL_BARRIER(XCTestCanStartTesting);
308
309 static dispatch_once_t onceToken;
310 dispatch_once (&onceToken, ^{
311 WAIT_FOR_BARRIER(XCTestHaveStarted);
312 });
313
314 // Scope test object suite under top level QtTestLib test run
315 [m_testRuns addObject:[QtTestLibTests testRun]];
316
317 NSString *suiteName = [NSString stringWithUTF8String:QTestResult::currentTestObjectName()];
318 pushTestRunForTest([XCTestSuite testSuiteWithName:suiteName], true);
319}
320
322{
323 popTestRun();
324}
325
326static bool isTestFunctionInActiveScope(const char *function)
327{
328 static NSString *testScope = [XCTestProbe testScope];
329
330 enum TestScope { Unknown, All, None, Self, Selected };
331 static TestScope activeScope = Unknown;
332
333 if (activeScope == Unknown) {
334 if ([testScope isEqualToString:@"All"])
335 activeScope = All;
336 else if ([testScope isEqualToString:@"None"])
337 activeScope = None;
338 else if ([testScope isEqualToString:@"Self"])
339 activeScope = Self;
340 else
341 activeScope = Selected;
342 }
343
344 if (activeScope == All)
345 return true;
346 else if (activeScope == None)
347 return false;
348 else if (activeScope == Self)
349 return true; // Investigate
350
351 Q_ASSERT(activeScope == Selected);
352
353 static NSArray<NSString *> *forcedTests = [@[ @"initTestCase", @"initTestCase_data", @"cleanupTestCase" ] retain];
354 if ([forcedTests containsObject:[NSString stringWithUTF8String:function]])
355 return true;
356
357 static NSArray<NSString *> *testsInScope = [[testScope componentsSeparatedByString:@","] retain];
358 bool inScope = [testsInScope containsObject:[NSString stringWithFormat:@"%s/%s",
359 QTestResult::currentTestObjectName(), function]];
360
361 if ([XCTestProbe isInverseTestScope])
362 inScope = !inScope;
363
364 return inScope;
365}
366
367void QXcodeTestLogger::enterTestFunction(const char *function)
368{
369 if (!isTestFunctionInActiveScope(function))
371
372 XCTest *test = [QtTestLibTest testCaseWithInvocation:nil];
373 pushTestRunForTest(test, !QTestResult::skipCurrentTest());
374}
375
377{
378 popTestRun();
379}
380
381void QXcodeTestLogger::addIncident(IncidentTypes type, const char *description,
382 const char *file, int line)
383{
384 XCTestRun *testRun = [m_testRuns lastObject];
385
386 // The 'expected' argument to recordFailureWithDescription refers to whether
387 // the failure was a regular failed assertion, or an unexpected exception,
388 // so in our case it's always 'YES', and we need to explicitly ignore XFail.
391 NSString *testCaseName = [[testRun test] nameForLegacyLogging];
392 QTest::qt_asprintf(&buf, "Test Case '%s' failed expectedly (%s).\n",
393 [testCaseName UTF8String], description);
394 outputString(buf.constData());
395 return;
396 }
397
399 // We ignore non-data passes, as we're already reporting that as part of the
400 // normal test case start/stop cycle.
402 return;
403
405 NSString *testCaseName = [[testRun test] nameForLegacyLogging];
406 QTest::qt_asprintf(&buf, "Test Case '%s' passed.\n", [testCaseName UTF8String]);
407 outputString(buf.constData());
408 return;
409 }
410
411 // FIXME: Handle blacklisted tests
412
413 if (!file || !description)
414 return; // Or report?
415
416 [testRun recordFailureWithDescription:[NSString stringWithUTF8String:description]
417 inFile:[NSString stringWithUTF8String:file] atLine:line expected:YES];
418}
419
420void QXcodeTestLogger::addMessage(MessageTypes type, const QString &message,
421 const char *file, int line)
422{
424
425 if (QTestLog::verboseLevel() > 0 && (file && line)) {
426 QTest::qt_asprintf(&buf, "%s:%d: ", file, line);
427 outputString(buf.constData());
428 }
429
431 XCTestRun *testRun = [m_testRuns lastObject];
432 NSString *testCaseName = [[testRun test] nameForLegacyLogging];
433 QTest::qt_asprintf(&buf, "Test Case '%s' skipped (%s).\n",
434 [testCaseName UTF8String], message.toUtf8().constData());
435 } else {
436 QTest::qt_asprintf(&buf, "%s\n", message.toUtf8().constData());
437 }
438
439 outputString(buf.constData());
440}
441
443{
445}
446
447void QXcodeTestLogger::pushTestRunForTest(XCTest *test, bool start)
448{
449 XCTestRun *testRun = [[test testRunClass] testRunWithTest:test];
450 [m_testRuns addObject:testRun];
451
452 if (start)
453 [testRun start];
454}
455
456XCTestRun *QXcodeTestLogger::popTestRun()
457{
458 XCTestRun *testRun = [[m_testRuns lastObject] retain];
459 [m_testRuns removeLastObject];
460
461 if ([testRun startDate])
462 [testRun stop];
463
464 [[m_testRuns lastObject] addTestRun:testRun];
465 [testRun release];
466
467 return testRun;
468}
469
471{
472 return s_currentTestLogger;
473}
474
NSString * testScope()
Base class for test loggers.
void outputString(const char *msg)
Convenience method to write msg to the output stream.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static int verboseLevel()
Definition qtestlog.cpp:609
static const char * currentGlobalDataTag()
static void setSkipCurrentTest(bool value)
static const char * currentDataTag()
static bool skipCurrentTest()
static int parseCommandLineArgument(const char *argument)
void addIncident(IncidentTypes type, const char *description, const char *file=nullptr, int line=0) override
This virtual method is called when an event occurs that relates to the resolution of the test.
void leaveTestFunction() override
This virtual method is called after a test function has completed, to match \l enterTestFunction().
static bool canLogTestProgress()
void enterTestFunction(const char *function) override
This virtual method is called before each test function is invoked.
static bool isActive()
~QXcodeTestLogger() override
void stopLogging() override
Called after the end of a test run.
void startLogging() override
Called before the start of a test run.
void addBenchmarkResult(const QBenchmarkResult &result) override
This virtual method is called after a benchmark has been run enough times to produce usable data.
void addMessage(MessageTypes type, const QString &message, const char *file=nullptr, int line=0) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
static ThreadBarriers * get()
static void initialize()
void wait(Barrier barrier)
void signal(Barrier barrier)
p1 load("image.bmp")
QDate date
[1]
QList< QVariant > arguments
XCTestSuiteRun * testRun()
XCTestDriver * sharedTestDriver()
int qt_asprintf(QTestCharBuffer *str, const char *format,...)
Q_TESTLIB_EXPORT const char * currentDataTag()
Returns the name of the current test data.
QString self
Definition language.cpp:58
#define Q_UNLIKELY(x)
#define QT_WARNING_POP
#define QT_WARNING_DISABLE_DEPRECATED
#define QT_WARNING_PUSH
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
static QDBusError::ErrorType get(const char *name)
@ None
Definition qhash.cpp:531
#define qFatal
Definition qlogging.h:168
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint GLsizei const GLchar * message
GLuint start
GLuint name
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
int runTests(QObject *testObject, int argc, char *argv[])
#define Q_UNUSED(x)
static XCTestSuiteRun * s_qtTestSuiteRun
#define WAIT_FOR_BARRIER(b)
#define SIGNAL_BARRIER(b)
#define FOREACH_BARRIER(cmd)
QFile file
[0]
QDateTime startDate(QDate(2012, 7, 6), QTime(8, 30, 0))
[14]
QDBusArgument argument