ComponentKitTests/CKOptionalTests.mm (312 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import <XCTest/XCTest.h> #import <string> #import <ComponentKit/CKOptional.h> using namespace CK; @interface CKOptionalTests : XCTestCase @end @implementation CKOptionalTests - (void)test_EqualityWithNone { Optional<int> const a = none; XCTAssert(a == none); XCTAssert(none == a); XCTAssert(none == none); XCTAssert(Optional<int>{} == none); XCTAssert(none == Optional<int>{}); auto const b = Optional<int>{2}; XCTAssert(b != none); XCTAssert(none != b); } - (void)test_Equality { auto const a = Optional<int>{2}; auto const b = Optional<int>{3}; auto const c = Optional<int>{}; auto const d = Optional<int>{2}; auto const e = Optional<int>{}; XCTAssert(a == d); XCTAssert(c == e); XCTAssert(a != b); XCTAssert(a != c); } - (void)test_EqualityWithValues { auto const a = Optional<int>{2}; XCTAssert(a == 2); XCTAssert(2 == a); XCTAssert(a != 3); XCTAssert(3 != a); } static auto toInt(const std::string& s) -> Optional<int> { if (s.empty()) { return none; } return std::stoi(s); } - (void)test_FlatMap { auto const a = Optional<std::string>{"123"}; auto const b = Optional<std::string>{""}; auto const c = Optional<std::string>{}; XCTAssert(a.flatMap(toInt) == 123); XCTAssert(b.flatMap(toInt) == none); XCTAssert(c.flatMap(toInt) == none); } struct HasOptional { Optional<int> x; }; - (void)test_FlatMap_PointerToMember { Optional<HasOptional> const a = HasOptional { 123 }; Optional<HasOptional> const b = HasOptional { none }; XCTAssert(a.flatMap(&HasOptional::x) == 123); XCTAssert(b.flatMap(&HasOptional::x) == none); } - (void)test_WhenEmpty_ValuePtrIsNull { auto const empty = Optional<int>{}; if (auto const x = empty.unsafeValuePtrOrNull()) { XCTFail(); } } - (void)test_WhenHasValue_ValuePtrPointsToValue { auto const a = Optional<int>{2}; if (auto i = a.unsafeValuePtrOrNull()) { XCTAssert(*i == 2); } else { XCTFail(); } } - (void)test_WhenHasValue_ValueOrTakingFunctionDoesNotInvokeIt { auto fail = ^{ XCTFail(); return 3; }; auto x = Optional<int>{2}; auto y = x.valueOr(fail); XCTAssertEqual(y, 2); auto z = toInt("2").valueOr(fail); XCTAssertEqual(z, 2); } static auto toNSString(int x) -> NSString * { return [NSString stringWithFormat:@"%d", x]; } - (void)test_MappingToPointer { auto const x = Optional<int>{2}; XCTAssertEqualObjects(x.mapToPtr(toNSString), @"2"); auto const y = Optional<int>{}; XCTAssertNil(y.mapToPtr(toNSString)); } struct TypeWithPointer { NSString *title; }; - (void)test_MappingToPointer_PointerToMember { Optional<TypeWithPointer> const a = TypeWithPointer { @"John Doe" }; Optional<TypeWithPointer> const b = TypeWithPointer { nil }; XCTAssertEqualObjects(a.mapToPtr(&TypeWithPointer::title), @"John Doe"); XCTAssertNil(b.mapToPtr(&TypeWithPointer::title)); } struct ConvertibleToInt { int i; operator int() const { return int{i}; } }; - (void)test_InitialisingFromConvertible { Optional<int> x = ConvertibleToInt{42}; XCTAssertEqual(x, 42); } - (void)test_AssigningFromConvertible { Optional<int> x = ConvertibleToInt{42}; auto const c = ConvertibleToInt{43}; x = c; XCTAssertEqual(x, 43); } static void increment(int &i) { i += 1; } - (void)test_MutatingApply { Optional<int> o = 42; o.apply(increment); XCTAssertEqual(o, 43); } struct SixteenBytes { uint64_t x; uint64_t y; }; - (void)compileTimeChecks { static_assert(std::is_trivially_destructible<Optional<CGFloat>>::value, "Optional must propagate trivial destructor"); static_assert(std::is_trivially_copyable<Optional<CGFloat>>::value && std::is_trivially_copy_constructible<Optional<CGFloat>>::value, "Optional must propagate trivial copy constructor"); static_assert(std::is_trivially_move_constructible<Optional<CGFloat>>::value, "Optional must propagate trivial move constructor"); static_assert(OptionalDetail::Storage<uint32_t>::HasValueSize == sizeof(uint32_t), "When wrapping the type of size 4, Optional storage must use the flag of the same size"); static_assert(OptionalDetail::Storage<uint64_t>::HasValueSize == sizeof(uint64_t), "When wrapping the type of size 8, Optional storage must use the flag of the same size"); static_assert(OptionalDetail::Storage<SixteenBytes>::HasValueSize == sizeof(uint64_t), "When wrapping the type of size 16, Optional storage must use the flag of size 8"); } @end static int numCopies = 0; static int numMoves = 0; struct CopyMoveTracker { CopyMoveTracker() {} CopyMoveTracker(const CopyMoveTracker &other) { numCopies += 1; } auto operator =(const CopyMoveTracker &) -> CopyMoveTracker & { numCopies += 1; return *this; } CopyMoveTracker(CopyMoveTracker &&other) { numMoves += 1; } auto operator =(CopyMoveTracker &&) -> CopyMoveTracker & { numMoves += 1; return *this; } }; @interface CKOptionalTests_CopiesAndMoves: XCTestCase @end @implementation CKOptionalTests_CopiesAndMoves - (void)setUp { [super setUp]; numCopies = 0; numMoves = 0; } - (void)test_CopyConstruction { auto const x = Optional<CopyMoveTracker>{CopyMoveTracker {}}; auto const y = x; XCTAssertEqual(numCopies, 1); } - (void)test_CopyConstructionFromValue { auto const x = CopyMoveTracker{}; __unused auto const y = Optional<CopyMoveTracker>{x}; XCTAssertEqual(numCopies, 1); } - (void)test_Matching { auto const x = Optional<CopyMoveTracker>{CopyMoveTracker {}}; x.match([](const CopyMoveTracker &){}, [](){}); XCTAssertEqual(numCopies, 0); } - (void)test_Mapping { auto const x = Optional<CopyMoveTracker>{CopyMoveTracker {}}; x.map([](const CopyMoveTracker &){ return 0; }); XCTAssertEqual(numCopies, 0); } - (void)test_WhenMapping_ReturnValueIsMoved { auto const x = Optional<CopyMoveTracker>{CopyMoveTracker {}}; // Move __unused auto const y = x.map([](const CopyMoveTracker &){ return CopyMoveTracker {}; }); // Move when constructing the resulting optional XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 2); } - (void)test_WhenHasValue_ValueOrCopiesValueOut { auto const x = Optional<CopyMoveTracker>{CopyMoveTracker {}}; // Move __unused auto const y = x.valueOr({}); // Copy the value out of the optional XCTAssertEqual(numCopies, 1); XCTAssertEqual(numMoves, 1); } - (void)test_WhenEmpty_ValueOrMovesDefaultValue { Optional<CopyMoveTracker> const x = none; __unused auto const y = x.valueOr({}); // Move XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 1); } static auto returnsOptional() -> Optional<CopyMoveTracker> { return CopyMoveTracker {}; } static auto returnsNone() -> Optional<CopyMoveTracker> { return none; } - (void)test_WhenMatchingOnRValueAndHasValue_MovesValueOut { __unused auto const y = returnsOptional() // Move .match([](CopyMoveTracker &&t){ return std::move(t); // Move }, [](){ return CopyMoveTracker {}; }); XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 2); } - (void)test_WhenRValueHasValue_ValueOrMovesValueOut { __unused auto const x = returnsOptional() // Move + Move .valueOr({}); XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 2); auto const dflt = CopyMoveTracker {}; __unused auto const y = returnsOptional() // Move + Move .valueOr(dflt); XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 4); __unused auto const z = returnsOptional() // Move + Move .valueOr([](){ return CopyMoveTracker {}; }); XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 6); } - (void)test_WhenRValueEmpty_ValueOrMovesDefaultValue { __unused auto const x = returnsNone() .valueOr({}); // Move XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 1); __unused auto const z = returnsNone() .valueOr([](){ return CopyMoveTracker {}; }); // RVO XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 1); } - (void)test_WhenRValueEmpty_ValueOrCopiesDefaultValue { auto const dflt = CopyMoveTracker {}; __unused auto const x = returnsNone() .valueOr(dflt); // Copy XCTAssertEqual(numCopies, 1); XCTAssertEqual(numMoves, 0); } - (void)test_WhenAssigningFromRValue_MovesValueOut { Optional<CopyMoveTracker> x = none; x = returnsOptional(); // Move + Move assign XCTAssertEqual(numCopies, 0); XCTAssertEqual(numMoves, 2); } - (void)test_WhenApplyingRValue_MovesValueOut { auto x = CopyMoveTracker{}; returnsOptional() // Move .apply([&](CopyMoveTracker &&t){ x = std::move(t); }); // Move assign XCTAssertEqual(numMoves, 2); } - (void)test_WhenApplyingMultipleNonEmptyOptionals_CallbackIsCalled { auto result = -1; CK::apply([&](int one, int two, int three){ result = one + two + three; }, Optional<int>(1), Optional<int>(2), Optional<int>(3)); XCTAssertEqual(result, 6); } - (void)test_WhenApplyingMultipleOptionalsAndOneOfThemIsNone_CallbackIsNotCalled { auto result = -1; const Optional<int> intNone = none; CK::apply([&](int one, int two, int three){ result = one + two + three; }, Optional<int>(1), intNone, Optional<int>(3)); CK::apply([&](int one, int two, int three){ result = one + two + three; }, intNone, Optional<int>(2), Optional<int>(3)); CK::apply([&](int one, int two, int three){ result = one + two + three; }, Optional<int>(1), Optional<int>(2), intNone); XCTAssertEqual(result, -1); } @end