// Copyright Epic Games, Inc. All Rights Reserved. #include "Catch2Includes.h" #include #include #include TEST_CASE("OpenAPI.StartAbortAndStartAgain") { int valueB = 0; int valueC = 0; AutoRTFM::Transact([&]() { // Recorded valueB as starting at 0. valueB = 20; AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::RecordOpenWrite(&valueB); valueB=10; AutoRTFM::AbortTransaction(); AutoRTFM::ForTheRuntime::ClearTransactionStatus(); AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::RecordOpenWrite(&valueC); valueC = 30; AutoRTFM::AbortTransaction(); }); }); // We rollback the transaction to the value we had when we first recorded the address REQUIRE(valueB == 20); REQUIRE(valueC == 0); } TEST_CASE("OpenAPI.CommitScopedFromOpen_Illegal", "[.]") { AutoRTFM::ETransactionResult transactResult = AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::CommitTransaction(); // illegal. Can't Commit from within a scoped transaction }); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest); } TEST_CASE("OpenAPI.RecordDataClosed_Illegal", "[.]") { int value = 0; AutoRTFM::ETransactionResult transactResult = AutoRTFM::Transact([&]() { REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { AutoRTFM::RecordOpenWrite(&value); // Illegal. Can't record writes explicitly while closed value = 1; })); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed); REQUIRE(value == 1); } TEST_CASE("OpenAPI.WriteDataInTheOpen") { int value = 0; auto transactResult = AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::RecordOpenWrite(&value); value = 1; }); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed); REQUIRE(value == 1); } TEST_CASE("OpenAPI.AbortTransactionScopedFromOpen") { auto transactResult = AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::AbortTransaction(); }); FAIL("AutoRTFM::Open failed to throw after an abort"); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest); } TEST_CASE("OpenAPI.AbortTransactionScopedFromClosed") { auto transactResult = AutoRTFM::Transact([&]() { REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { AutoRTFM::AbortTransaction(); })); FAIL("AutoRTFM::Close should have no-op'ed because it's already closed from the Transact"); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest); } TEST_CASE("OpenAPI.AbortTransactionDoubleScopedFromOpen") { unsigned long value = -42; auto transactResult = AutoRTFM::Transact([&]() { value = 42; auto transactResult2 = AutoRTFM::Transact([&]() { value = 42424242; AutoRTFM::Open([&]() { AutoRTFM::AbortTransaction(); }); FAIL("AutoRTFM::Open failed to throw after an abort"); value = 24242424; }); if (transactResult2 != AutoRTFM::ETransactionResult::AbortedByRequest) FAIL("transactResult2 != AutoRTFM::ETransactionResult::AbortedByRequest"); if (value != 42) FAIL("value != 42"); value = 123123123; }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed); REQUIRE(value == 123123123); } TEST_CASE("OpenAPI.NestedClosedTransactions") { int value = 0x12345678; auto transactResult = AutoRTFM::Transact([&]() { // Read value int x = value; value = 0x11111111; auto transactResult2 = AutoRTFM::Transact([&]() { if (value != 0x11111111) FAIL("value != 0x11111111"); // Read value int y = value; value = 0x22222222; auto transactResult3 = AutoRTFM::Transact([&]() { if (value != 0x22222222) FAIL("value != 0x22222222"); // Read value int z = value; value = 0x33333333; auto transactResult4 = AutoRTFM::Transact([&]() { if (value != 0x33333333) FAIL("value != 0x33333333"); // Read value int q = value; value = 0x44444444; if (value != 0x44444444) FAIL("value != 0x44444444"); if (q != 0x33333333) FAIL("q != 0x33333333"); }); (void)transactResult4; if (value != 0x44444444) FAIL("value != 0x44444444"); if (z != 0x22222222) FAIL("z != 0x22222222"); }); (void)transactResult3; value = 0x55555555; if (value != 0x55555555) FAIL("value != 0x55555555"); if (y != 0x11111111) FAIL("y != 0x11111111"); }); (void)transactResult2; if (value != 0x55555555) FAIL("value != 0x55555555"); value = 0x66666666; if (value != 0x66666666) FAIL("value != 0x66666666"); if (x != 0x12345678) FAIL("x != 0x12345678"); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed); REQUIRE(value == 0x66666666); } TEST_CASE("OpenAPI.OpenWithCopy") { struct SomeData_t { int A; float B; char C; }; SomeData_t SomeData1{ 1,2.0,'3' }; auto transactResult = AutoRTFM::Transact([&]() { SomeData_t SomeData2{ 9,8.0,'7' }; SomeData1.A = 11; SomeData2.A = 29; AutoRTFM::Open([=]() { REQUIRE(SomeData1.A == 11); REQUIRE(SomeData2.A == 29); }); }); REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed); } #if defined(_BROKEN_ALLOC_FIXED_) TEST_CASE("OpenAPI.OpenCloseOpenClose") { // START OPEN REQUIRE(!AutoRTFM::IsTransactional()); int x = 42; std::vector v; std::map> m; v.push_back(100); m[1].push_back(2); m[1].push_back(3); m[4].push_back(5); m[6].push_back(7); m[6].push_back(8); m[6].push_back(9); auto transactResult = AutoRTFM::Transact([&]() { // A - WE ARE CLOSED if (!AutoRTFM::IsClosed()) FAIL("A - NOT CLOSED AS EXPECTED!"); // ------------------------------------- AutoRTFM::Open([&]() { // B - WE ARE OPEN REQUIRE(!AutoRTFM::IsClosed()); // ------------------------------------- REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { // C - WE ARE CLOSED AGAIN if (!AutoRTFM::IsClosed()) FAIL("C - NOT CLOSED AS EXPECTED!"); // ------------------------------------- AutoRTFM::Open([&]() { // D - WE ARE OPEN AGAIN REQUIRE(!AutoRTFM::IsClosed()); // An abort here will set state on the transactions, but will not LongJump // AutoRTFM::Abort(); }); // ------------------------------------- // E - BACK TO CLOSED AFTER AN OPEN x = 5; for (size_t n = 10; n--;) v.push_back(2 * n); m.clear(); m[10].push_back(11); m[12].push_back(13); m[12].push_back(14); // An abort here is closed and will LongJump past F AND G all the way to H AutoRTFM::AbortTransaction(); // ------------------------------------- AutoRTFM::Open([&]() { // F - WE ARE OPEN AGAIN // REQUIRE(!AutoRTFM::IsClosed()); }); // ------------------------------------- // G - BACK TO CLOSED AGAIN if (!AutoRTFM::IsClosed()) FAIL("NOT CLOSED!"); })); // ------------------------------------- // H - BACK TO OPEN REQUIRE(!AutoRTFM::IsClosed()); }); // ------------------------------------- // I - Finally closed again to finish out the transaction if (!AutoRTFM::IsClosed()) FAIL("I - NOT CLOSED AS EXPECTED!"); }); REQUIRE( AutoRTFM::ETransactionResult::AbortedByRequest == transactResult); REQUIRE(x == 42); REQUIRE(v.size() == 1); REQUIRE(v[0] == 100); REQUIRE(m.size() == 3); REQUIRE(m[1].size() == 2); REQUIRE(m[1][0] == 2); REQUIRE(m[1][1] == 3); REQUIRE(m[4].size() == 1); REQUIRE(m[4][0] == 5); REQUIRE(m[6].size() == 3); REQUIRE(m[6][0] == 7); REQUIRE(m[6][1] == 8); REQUIRE(m[6][2] == 9); REQUIRE(!AutoRTFM::IsTransactional()); } #endif TEST_CASE("OpenAPI.Commit_TransactOpenCloseCommit") { REQUIRE(!AutoRTFM::IsTransactional()); // We're open REQUIRE(!AutoRTFM::IsClosed()); int value = 10; value++; // Close and start the top-level transaction AutoRTFM::Transact([&]() { if (!AutoRTFM::IsClosed()) FAIL("Not Closed"); AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { value = 42; })); REQUIRE(value == 42); // RTFM writes through immediately, so we can see this value in the open AutoRTFM::ForTheRuntime::CommitTransaction(); }); if (value != 42) FAIL("Value != 42!"); value = 420; }); REQUIRE(value == 420); REQUIRE(!AutoRTFM::IsTransactional()); } TEST_CASE("OpenAPI.Commit_TransactOpenCloseAbort") { REQUIRE(!AutoRTFM::IsTransactional()); // We're open REQUIRE(!AutoRTFM::IsClosed()); int Value = 10; Value++; // Close and start the top-level transaction AutoRTFM::Transact([&]() { if (!AutoRTFM::IsClosed()) FAIL("Not Closed"); AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { int Local = 0; Local = 42; Value = Local; })); AutoRTFM::AbortTransaction(); // undoes Value = 42 in the open }); FAIL("Should not reach here!"); }); REQUIRE(Value == 11); REQUIRE(!AutoRTFM::IsTransactional()); } TEST_CASE("OpenAPI.DoubleTransact") { double value = 1.0; AutoRTFM::Transact([&]() { AutoRTFM::Transact([&]() { value *= 2.5; AutoRTFM::AbortTransaction(); }); value *= 10.0; }); REQUIRE(value == 10.0); } TEST_CASE("OpenAPI.DoubleTransact2") { double value = 1.0; AutoRTFM::Transact([&]() { value = value + 2.0; AutoRTFM::Transact([&]() { if (value == 3.0) { value *= 2.5; } if (value == 7.5) AutoRTFM::AbortTransaction(); }); value *= 10.0; }); REQUIRE(value == 30.0); } TEST_CASE("OpenAPI.DoubleTransact3") { double result = 0.0; AutoRTFM::Transact([&]() { double value = 1.0; value = value + 2.0; AutoRTFM::Transact([&]() { if (value == 3.0) { value *= 2.5; } if (value == 7.5) AutoRTFM::AbortTransaction(); }); value *= 10.0; result = value; }); REQUIRE(result == 30.0); } TEST_CASE("OpenAPI.StackWriteCommitInTheOpen1") { int value = 0; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::RecordOpenWrite(&value); value = 10; AutoRTFM::ForTheRuntime::CommitTransaction(); REQUIRE(value == 10); }); }); } TEST_CASE("OpenAPI.StackWriteCommitInTheOpen2") { int Value = 0; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { AutoRTFM::Open([&]() { AutoRTFM::RecordOpenWrite(&Value); Value = 10; }); })); AutoRTFM::ForTheRuntime::CommitTransaction(); REQUIRE(Value == 10); }); }); } TEST_CASE("OpenAPI.StackWriteAbortInTheOpen1") { int value = 0; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::RecordOpenWrite(&value); value = 10; AutoRTFM::AbortTransaction(); REQUIRE(value == 0); }); }); } #if defined(OPENAPI_ILLEGAL_TESTS) TEST_CASE("OpenAPI.StackWriteCommitInTheOpen3_Illegal") { AutoRTFM::Transact([&]() { int value = 0; AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::ForTheRuntime::WriteMemory(&value, 10); AutoRTFM::ForTheRuntime::CommitTransaction(); REQUIRE(value == 10); }); }); } #endif int value1 = 0; TEST_CASE("OpenAPI.WriteMemory1") { const int sourceValue = 10; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::ForTheRuntime::WriteMemory(&value1, &sourceValue); AutoRTFM::ForTheRuntime::CommitTransaction(); REQUIRE(value1 == 10); }); }); } TEST_CASE("OpenAPI.StackWriteAbortInTheOpen2") { int value = 0; int bGotToA = false; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::ForTheRuntime::WriteMemory(&value, 10); // Illegal to write to value because it's in the inner-most closed-nest AutoRTFM::AbortTransaction(); }); // Never gets here bGotToA = true; REQUIRE(value == 0); }); REQUIRE(bGotToA == false); REQUIRE(value == 0); } TEST_CASE("OpenAPI.WriteTrivialStructure") { struct SomeData { int A; double B; float C; char D; long E[5]; }; SomeData data = { 1,2.0, 3.0f, 'q', {123,234,345,456,567} }; SomeData data2 = { 9,8.0, 7.0f, '^', {999,888,777,666,555} }; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::ForTheRuntime::WriteMemory(&data, &data2); REQUIRE(data.A == 9); REQUIRE(data.B == 8.0); REQUIRE(data.C == 7.0f); REQUIRE(data.D == '^'); REQUIRE(data.E[0] == 999); REQUIRE(data.E[1] == 888); REQUIRE(data.E[2] == 777); REQUIRE(data.E[3] == 666); REQUIRE(data.E[4] == 555); AutoRTFM::AbortTransaction(); REQUIRE(data.A == 1); REQUIRE(data.B == 2.0); REQUIRE(data.C == 3.0f); REQUIRE(data.D == 'q'); REQUIRE(data.E[0] == 123); REQUIRE(data.E[1] == 234); REQUIRE(data.E[2] == 345); REQUIRE(data.E[3] == 456); REQUIRE(data.E[4] == 567); }); }); } TEST_CASE("OpenAPI.WriteTrivialStructure2") { struct SomeData { int A; double B; float C; char D; long E[5]; }; SomeData data = { 1,2.0, 3.0f, 'q', {123,234,345,456,567} }; SomeData data2 = { 9,8.0, 7.0f, '^', {999,888,777,666,555} }; SomeData data3 = { 19,28.0, 37.0f, '@', {4999,5888,6777,7666,8555} }; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); AutoRTFM::ForTheRuntime::WriteMemory(&data, &data2); REQUIRE(data.A == 9); REQUIRE(data.B == 8.0); REQUIRE(data.C == 7.0f); REQUIRE(data.D == '^'); REQUIRE(data.E[0] == 999); REQUIRE(data.E[1] == 888); REQUIRE(data.E[2] == 777); REQUIRE(data.E[3] == 666); REQUIRE(data.E[4] == 555); AutoRTFM::ForTheRuntime::WriteMemory(&data, &data3); REQUIRE(data.A == 19); REQUIRE(data.B == 28.0); REQUIRE(data.C == 37.0f); REQUIRE(data.D == '@'); REQUIRE(data.E[0] == 4999); REQUIRE(data.E[1] == 5888); REQUIRE(data.E[2] == 6777); REQUIRE(data.E[3] == 7666); REQUIRE(data.E[4] == 8555); AutoRTFM::AbortTransaction(); REQUIRE(data.A == 1); REQUIRE(data.B == 2.0); REQUIRE(data.C == 3.0f); REQUIRE(data.D == 'q'); REQUIRE(data.E[0] == 123); REQUIRE(data.E[1] == 234); REQUIRE(data.E[2] == 345); REQUIRE(data.E[3] == 456); REQUIRE(data.E[4] == 567); }); }); } TEST_CASE("OpenAPI.Footgun1") { int valueA = 0; int valueB = 0; AutoRTFM::Transact([&]() { // Does nothing - already closed REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { // Recorded valueB as starting at 0. valueB = 123; AutoRTFM::Open([&]() { // Unrecorded assignments in the open valueA = 10; valueB = 10; }); // valueA is now recorded as starting at 10 valueA = 20; AutoRTFM::AbortTransaction(); })); }); // We rollback the transaction to the value we had when we first recorded the address REQUIRE(valueA == 10); REQUIRE(valueB == 0); } TEST_CASE("OpenAPI.Footgun2") { int valueB = 0; int valueC = 0; AutoRTFM::Transact([&]() { // Does nothing - already closed REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { // Recorded valueB as starting at 0. valueB = 20; AutoRTFM::Open([&]() { // Unrecorded assignments in the open valueB = 10; valueC = 10; AutoRTFM::RecordOpenWrite(&valueC); // valueC was recorded in the open after the change - too late }); // valueA is now recorded as starting at 10 valueC = 40; AutoRTFM::AbortTransaction(); })); }); // We rollback the transaction to the value we had when we first recorded the address REQUIRE(valueB == 0); REQUIRE(valueC == 10); } #if 0 TEST_CASE("OpenAPI.StartCloseOnCommit") { REQUIRE(!AutoRTFM::IsTransactional()); int value = 10; value++; (void)value; AutoRTFM::ForTheRuntime::StartTransaction(); // Can't close outside of a transaction REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { value = 420; })); // assignment within the close should be visible to us REQUIRE(value == 420); // Setting a value in the open requires us to register the memory address with the transaction value = 42; AutoRTFM::RecordOpenWrite(&value); AutoRTFM::ForTheRuntime::CommitTransaction(); // Finally, 42 is committed to value REQUIRE(value == 42); REQUIRE(!AutoRTFM::IsTransactional()); } #endif TEST_CASE("OpenAPI.TransOpenStartCloseAbortAbort") { bool bGetsToA = false; bool bGetsToB = false; bool bGetsToC = false; bool bGetsToD = false; REQUIRE(!AutoRTFM::IsTransactional()); int Value = 10; AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::ForTheRuntime::StartTransaction(); Value++; Value = 42; AutoRTFM::EContextStatus CloseStatus = AutoRTFM::Close([&]() { Value = 420; AutoRTFM::AbortTransaction(); bGetsToA = true; }); REQUIRE(CloseStatus == AutoRTFM::EContextStatus::AbortedByRequest); AutoRTFM::ForTheRuntime::ClearTransactionStatus(); REQUIRE(bGetsToA == false); bGetsToB = true; REQUIRE(Value == 42); AutoRTFM::AbortTransaction(); bGetsToC = true; REQUIRE(Value == 42); }); bGetsToD = true; }); REQUIRE(bGetsToA == false); REQUIRE(bGetsToB == true); REQUIRE(bGetsToC == true); REQUIRE(bGetsToD == false); REQUIRE(!AutoRTFM::IsTransactional()); } TEST_CASE("OpenAPI.TransOpenTransCloseAbortAbort") { bool bGetsToA = false; bool bGetsToB = false; bool bGetsToC = false; bool bGetsToD = false; REQUIRE(!AutoRTFM::IsTransactional()); AutoRTFM::Transact([&]() { AutoRTFM::Open([&]() { AutoRTFM::Transact([&]() { int value = 10; value++; value = 42; // Can't close outside of a Transact REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]() { value = 420; AutoRTFM::AbortTransaction(); bGetsToA = true; })); REQUIRE(bGetsToA == false); bGetsToB = true; REQUIRE(value == 42); AutoRTFM::AbortTransaction(); bGetsToC = true; REQUIRE(value == 42); }); }); bGetsToD = true; }); REQUIRE(bGetsToA == false); REQUIRE(bGetsToB == false); REQUIRE(bGetsToC == false); REQUIRE(bGetsToD == true); REQUIRE(!AutoRTFM::IsTransactional()); }