Skip to content

Commit

Permalink
Adding TestUtil
Browse files Browse the repository at this point in the history
  • Loading branch information
gershnik committed Jan 7, 2024
1 parent 955f9dd commit 51306f2
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 1 deletion.
102 changes: 102 additions & 0 deletions include/objc-helpers/TestUtil.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2023 Eugene Gershnik
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file or at
https://github.com/gershnik/objc-helpers/blob/main/LICENSE
*/

#ifndef HEADER_BOX_UTIL_INCLUDED
#define HEADER_BOX_UTIL_INCLUDED

#import <XCTest/XCTest.h>

#include <sstream>
#include <cxxabi.h>

namespace TestUtil {
using std::to_string;

template<class T>
concept TestDescriptable = requires(const T & obj) {
{ testDescription(obj) } -> std::same_as<NSString *>;
};


template<class T>
concept ToStringDescriptable = requires(T obj) {
{ to_string(obj) } -> std::same_as<std::string>;
};


template<class T>
concept OStreamDescriptable = requires(T obj, std::ostream & str) {
{ str << obj };
};

inline auto demangle(const char * name) -> std::string {

int status = 0;
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
return (status==0) ? res.get() : name ;
}

template<class T>
auto describeForTest(const T & val) {
if constexpr (TestDescriptable<T>) {
return testDescription(val);
}
else if constexpr (ToStringDescriptable<T>) {
using std::to_string;
auto str = to_string(val);
return @(str.c_str());
} else if constexpr (OStreamDescriptable<T>) {
std::ostringstream str;
str << val;
return @(str.str().c_str());
} else {
return [NSString stringWithFormat:@"%s object", demangle(typeid(T).name()).c_str()];
}
}
}

#define XCTPrimitiveAssertCpp(test, op, type, expression1, expressionStr1, expression2, expressionStr2, ...) \
({ \
_XCT_TRY { \
__typeof__(expression1) expressionValue1 = (expression1); \
__typeof__(expression2) expressionValue2 = (expression2); \
if (expressionValue1 op expressionValue2) { \
_XCTRegisterFailure(test, _XCTFailureDescription((type), 0, expressionStr1, expressionStr2, TestUtil::describeForTest(expressionValue1), TestUtil::describeForTest(expressionValue2)), __VA_ARGS__); \
} \
} \
_XCT_CATCH (_XCTestCaseInterruptionException *interruption) { [interruption raise]; } \
_XCT_CATCH (...) { \
NSString *_xct_reason = _XCTGetCurrentExceptionReason(); \
_XCTRegisterUnexpectedFailure(test, _XCTFailureDescription((type), 1, expressionStr1, expressionStr2, _xct_reason), __VA_ARGS__); \
} \
})

#define XCTAssertCppEqual(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, !=, _XCTAssertion_Equal, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

#define XCTAssertCppNotEqual(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, ==, _XCTAssertion_NotEqual, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

#define XCTAssertCppGreaterThan(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, <=, _XCTAssertion_GreaterThan, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

#define XCTAssertCppGreaterThanOrEqual(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, <, _XCTAssertion_GreaterThanOrEqual, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

#define XCTAssertCppLessThan(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, >=, _XCTAssertion_LessThan, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

#define XCTAssertCppLessThanOrEqual(expression1, expression2, ...) \
XCTPrimitiveAssertCpp(nil, >, _XCTAssertion_LessThanOrEqual, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)


#endif

197 changes: 197 additions & 0 deletions test/TestUtilTests.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#include <objc-helpers/TestUtil.h>

#include "doctest.h"

using namespace std::literals;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion"

struct Failure {
std::string filePath;
NSUInteger lineNumber;
bool expected;
std::string condition;
std::string message;
};

static std::vector<Failure> failures;


void _XCTFailureHandler(XCTestCase * _Nullable test, BOOL expected, const char *filePath, NSUInteger lineNumber, NSString *condition, NSString * _Nullable format, ...) {

CHECK((__bridge void *)test == nullptr);
va_list vl;
va_start(vl, format);
auto message = [[NSString alloc] initWithFormat:format arguments:vl];
failures.push_back({filePath, lineNumber, expected != 0, condition.UTF8String, message.UTF8String});
va_end(vl);
}

#define CHECK_NO_FAILURES() CHECK(failures.empty());

#define CHECK_FAILURE(line, exp, cond, mes) REQUIRE(failures.size() == 1); \
CHECK(failures.back().filePath == __FILE__); \
CHECK(failures.back().lineNumber == (line)); \
CHECK(failures.back().expected == (exp)); \
CHECK(failures.back().condition == (cond)); \
CHECK(failures.back().message == (mes));


TEST_SUITE_BEGIN( "TestUtilTests" );


TEST_CASE( "integer" ) {

//Equal
failures.clear();
XCTAssertCppEqual(1, 1);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppEqual(1, 2);
CHECK_FAILURE(__LINE__ - 1, true, "((1) equal to (2)) failed: (\"1\") is not equal to (\"2\")", "");

failures.clear();
XCTAssertCppEqual(1, 2, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((1) equal to (2)) failed: (\"1\") is not equal to (\"2\")", "hello 1");

//Not Equal
failures.clear();
XCTAssertCppNotEqual(1, 2);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppNotEqual(1, 1);
CHECK_FAILURE(__LINE__ - 1, true, "((1) not equal to (1)) failed: (\"1\") is equal to (\"1\")", "");

failures.clear();
XCTAssertCppNotEqual(1, 1, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((1) not equal to (1)) failed: (\"1\") is equal to (\"1\")", "hello 1");

//Greater Than
failures.clear();
XCTAssertCppGreaterThan(2, 1);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppGreaterThan(1, 2);
CHECK_FAILURE(__LINE__ - 1, true, "((1) greater than (2)) failed: (\"1\") is not greater than (\"2\")", "");

failures.clear();
XCTAssertCppGreaterThan(2, 2);
CHECK_FAILURE(__LINE__ - 1, true, "((2) greater than (2)) failed: (\"2\") is not greater than (\"2\")", "");

failures.clear();
XCTAssertCppGreaterThan(1, 2, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((1) greater than (2)) failed: (\"1\") is not greater than (\"2\")", "hello 1");

//Greater Than or Equal
failures.clear();
XCTAssertCppGreaterThanOrEqual(2, 1);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppGreaterThanOrEqual(2, 2);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppGreaterThanOrEqual(1, 2);
CHECK_FAILURE(__LINE__ - 1, true, "((1) greater than or equal to (2)) failed: (\"1\") is less than (\"2\")", "");

failures.clear();
XCTAssertCppGreaterThanOrEqual(1, 2, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((1) greater than or equal to (2)) failed: (\"1\") is less than (\"2\")", "hello 1");

//Less than
failures.clear();
XCTAssertCppLessThan(4, 5);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppLessThan(5, 4);
CHECK_FAILURE(__LINE__ - 1, true, "((5) less than (4)) failed: (\"5\") is not less than (\"4\")", "");

failures.clear();
XCTAssertCppLessThan(5, 5);
CHECK_FAILURE(__LINE__ - 1, true, "((5) less than (5)) failed: (\"5\") is not less than (\"5\")", "");

failures.clear();
XCTAssertCppLessThan(5, 4, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((5) less than (4)) failed: (\"5\") is not less than (\"4\")", "hello 1");

//Less Than or Equal
failures.clear();
XCTAssertCppLessThanOrEqual(4, 5);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppLessThanOrEqual(4, 4);
CHECK_NO_FAILURES();

failures.clear();
XCTAssertCppLessThanOrEqual(5, 4);
CHECK_FAILURE(__LINE__ - 1, true, "((5) less than or equal to (4)) failed: (\"5\") is greater than (\"4\")", "");

failures.clear();
XCTAssertCppLessThanOrEqual(5, 4, "hello %d", 1);
CHECK_FAILURE(__LINE__ - 1, true, "((5) less than or equal to (4)) failed: (\"5\") is greater than (\"4\")", "hello 1");
}

TEST_CASE( "string" ) {

failures.clear();
XCTAssertCppEqual("a"s, "b"s);
CHECK_FAILURE(__LINE__ - 1, true, "((\"a\"s) equal to (\"b\"s)) failed: (\"a\") is not equal to (\"b\")", "");

}

namespace {
struct foo {
int i;

friend bool operator==(foo, foo) = default;

friend NSString * testDescription(foo f) { return [NSString stringWithFormat:@"%d", f.i]; }
};
}

TEST_CASE( "TestDescriptable" ) {
failures.clear();
XCTAssertCppEqual(foo{1}, foo{2});
CHECK_FAILURE(__LINE__ - 1, true, "((foo{1}) equal to (foo{2})) failed: (\"1\") is not equal to (\"2\")", "");
}

namespace {
struct bar {
int i;

friend bool operator==(bar, bar) = default;
};
}

TEST_CASE( "Generic" ) {
failures.clear();
XCTAssertCppEqual(bar{1}, bar{2});
CHECK_FAILURE(__LINE__ - 1, true, "((bar{1}) equal to (bar{2})) failed: (\"(anonymous namespace)::bar object\") is not equal to (\"(anonymous namespace)::bar object\")", "");
}

namespace {
struct baz {
int i;

friend bool operator==(baz, baz) {
throw std::runtime_error("ha");
}
};
}

TEST_CASE( "Exception" ) {
failures.clear();
XCTAssertCppEqual(baz{1}, baz{2});
CHECK_FAILURE(__LINE__ - 1, false, "((baz{1}) equal to (baz{2})) failed: throwing \"std::runtime_error: ha\"", "");
}

TEST_SUITE_END();

#pragma clang diagnostic pop
22 changes: 21 additions & 1 deletion test/test.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
441779392B24C6B00036AF9F /* NSObjectUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 441779382B24C6B00036AF9F /* NSObjectUtilTests.mm */; };
4417793B2B26FEA70036AF9F /* CoDispatchTestsNoexcept.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4417793A2B26FEA60036AF9F /* CoDispatchTestsNoexcept.cpp */; settings = {COMPILER_FLAGS = "-fno-exceptions"; }; };
44B947E72B477A2700B68C7E /* BoxUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44B947E62B477A2700B68C7E /* BoxUtilTests.mm */; };
44B948032B4A4EE500B68C7E /* TestUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44B947E92B49A75400B68C7E /* TestUtilTests.mm */; };
44B948042B4A4EF600B68C7E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44B947FF2B49ACF900B68C7E /* XCTest.framework */; settings = {ATTRIBUTES = (Required, ); }; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -71,13 +73,17 @@
4417793A2B26FEA60036AF9F /* CoDispatchTestsNoexcept.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CoDispatchTestsNoexcept.cpp; sourceTree = "<group>"; };
44B947E52B4778EB00B68C7E /* BoxUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BoxUtil.h; sourceTree = "<group>"; };
44B947E62B477A2700B68C7E /* BoxUtilTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BoxUtilTests.mm; sourceTree = "<group>"; };
44B947E82B49A6ED00B68C7E /* TestUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestUtil.h; sourceTree = "<group>"; };
44B947E92B49A75400B68C7E /* TestUtilTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestUtilTests.mm; sourceTree = "<group>"; };
44B947FF2B49ACF900B68C7E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
4417790F2B20136E0036AF9F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
44B948042B4A4EF600B68C7E /* XCTest.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -90,14 +96,16 @@
441779212B202F3E0036AF9F /* Library */,
4417791C2B2013E00036AF9F /* doctest.h */,
441779152B20136E0036AF9F /* main.mm */,
44B947E62B477A2700B68C7E /* BoxUtilTests.mm */,
4417791F2B202DA30036AF9F /* CoDispatchTests.mm */,
441779342B2235B70036AF9F /* CoDispatchTestsCpp.cpp */,
4417793A2B26FEA60036AF9F /* CoDispatchTestsNoexcept.cpp */,
4417791D2B201E280036AF9F /* NSStringUtilTests.mm */,
44B947E62B477A2700B68C7E /* BoxUtilTests.mm */,
441779362B24C4930036AF9F /* NSNumberUtilTests.mm */,
441779382B24C6B00036AF9F /* NSObjectUtilTests.mm */,
44B947E92B49A75400B68C7E /* TestUtilTests.mm */,
441779132B20136E0036AF9F /* Products */,
44B947EB2B49A7BD00B68C7E /* Frameworks */,
);
sourceTree = "<group>";
};
Expand All @@ -118,11 +126,20 @@
441779242B202F530036AF9F /* NSNumberUtil.h */,
441779232B202F530036AF9F /* NSObjectUtil.h */,
441779262B202F530036AF9F /* NSStringUtil.h */,
44B947E82B49A6ED00B68C7E /* TestUtil.h */,
);
name = Library;
path = "../include/objc-helpers";
sourceTree = "<group>";
};
44B947EB2B49A7BD00B68C7E /* Frameworks */ = {
isa = PBXGroup;
children = (
44B947FF2B49ACF900B68C7E /* XCTest.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -210,6 +227,7 @@
441779372B24C4930036AF9F /* NSNumberUtilTests.mm in Sources */,
44B947E72B477A2700B68C7E /* BoxUtilTests.mm in Sources */,
4417791E2B201E280036AF9F /* NSStringUtilTests.mm in Sources */,
44B948032B4A4EE500B68C7E /* TestUtilTests.mm in Sources */,
441779352B2235B70036AF9F /* CoDispatchTestsCpp.cpp in Sources */,
441779392B24C6B00036AF9F /* NSObjectUtilTests.mm in Sources */,
4417793B2B26FEA70036AF9F /* CoDispatchTestsNoexcept.cpp in Sources */,
Expand Down Expand Up @@ -355,6 +373,7 @@
GCC_WARN_PEDANTIC = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LD_RUNPATH_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
OTHER_CFLAGS = (
"-fprofile-instr-generate",
"-fcoverage-mapping",
Expand All @@ -380,6 +399,7 @@
GCC_WARN_PEDANTIC = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LD_RUNPATH_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
OTHER_CFLAGS = (
"-fprofile-instr-generate",
"-fcoverage-mapping",
Expand Down

0 comments on commit 51306f2

Please sign in to comment.