From 1f28fc6836be6aeaaee0d97bd79aeb854f1cf302 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 14 Mar 2021 13:08:53 -0300 Subject: [PATCH] Add LongRunningTradesTest This new api testcase can run long series' of regtest trades by looping over modified TakeBuyBTCOfferTest and TakeSellBTCOfferTest cases. The purpose is to help reproduce problems and isolate bugs Bisq's core and api layers. LongRunningTradesTest is not enabled by default; it will not run in a default test environment (such a Travis CI). Enviornment variable LONG_RUNNING_TRADES_TEST_ENABLED must exist for the test to run. The env variable can be set in a bash shell before running the test case in a shell (using a gradle command), or the environment variable can be set in an Intellij test launcher's Evironment variables field. The modifed (short running) TakeBuyBTCOfferTest and TakeSellBTCOfferTest cases run as before. Changes include: - Add static boolean isLongRunningTest to AbstractOfferTest. - Add looping control Supplier maxTradeStateAndPhaseChecks to AbstractTradeTest. It uses the isLongRunningTest flag in the superclass to help define the wait times for trade state/phase changes during short and long running tests. - Made AbstractTradeTest assert(true, trade.isDepositPublished) conditional upon isLongRunningTest value. Long running trade test asserts have to be looser due to increasing latency of wallet, offer and trade operations in the server as the trade counts increase. - Overload ApiTestCase#startSupportingApps with additional flag: startSupportingAppsInDebugMode. Default is false. This makes starting background apps in debug mode a bit more convenient and self explanatory. --- .../java/bisq/apitest/method/MethodTest.java | 26 +++- .../method/offer/AbstractOfferTest.java | 4 + .../method/trade/AbstractTradeTest.java | 9 +- .../method/trade/TakeBuyBTCOfferTest.java | 146 +++++++++++++++--- .../method/trade/TakeSellBTCOfferTest.java | 140 ++++++++++++++--- .../scenario/LongRunningTradesTest.java | 100 ++++++++++++ 6 files changed, 372 insertions(+), 53 deletions(-) create mode 100644 apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 26765f6ccb..c475c909ec 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -55,11 +55,23 @@ public class MethodTest extends ApiTestCase { boolean registerDisputeAgents, boolean generateBtcBlock, Enum... supportingApps) { + startSupportingApps(callRateMeteringConfigFile, + registerDisputeAgents, + generateBtcBlock, + false, + supportingApps); + } + + public static void startSupportingApps(File callRateMeteringConfigFile, + boolean registerDisputeAgents, + boolean generateBtcBlock, + boolean startSupportingAppsInDebugMode, + Enum... supportingApps) { try { setUpScaffold(new String[]{ "--supportingApps", toNameList.apply(supportingApps), "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(), - "--enableBisqDebugging", "false" + "--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false" }); doPostStartup(registerDisputeAgents, generateBtcBlock); } catch (Exception ex) { @@ -70,13 +82,23 @@ public class MethodTest extends ApiTestCase { public static void startSupportingApps(boolean registerDisputeAgents, boolean generateBtcBlock, Enum... supportingApps) { + startSupportingApps(registerDisputeAgents, + generateBtcBlock, + false, + supportingApps); + } + + public static void startSupportingApps(boolean registerDisputeAgents, + boolean generateBtcBlock, + boolean startSupportingAppsInDebugMode, + Enum... supportingApps) { try { // Disable call rate metering where there is no callRateMeteringConfigFile. File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig(); setUpScaffold(new String[]{ "--supportingApps", toNameList.apply(supportingApps), "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(), - "--enableBisqDebugging", "false" + "--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false" }); doPostStartup(registerDisputeAgents, generateBtcBlock); } catch (Exception ex) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index 095e4f0d1d..c28a51de42 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -23,6 +23,7 @@ import org.bitcoinj.utils.Fiat; import java.math.BigDecimal; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; @@ -45,6 +46,9 @@ import bisq.apitest.method.MethodTest; @Slf4j public abstract class AbstractOfferTest extends MethodTest { + @Setter + protected static boolean isLongRunningTest; + @BeforeAll public static void setUp() { startSupportingApps(true, diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 24e40d313c..161d2f1283 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -2,6 +2,8 @@ package bisq.apitest.method.trade; import bisq.proto.grpc.TradeInfo; +import java.util.function.Supplier; + import org.slf4j.Logger; import org.junit.jupiter.api.BeforeAll; @@ -22,6 +24,8 @@ public class AbstractTradeTest extends AbstractOfferTest { // A Trade ID cache for use in @Test sequences. protected static String tradeId; + protected final Supplier maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2; + @BeforeAll public static void initStaticFixtures() { EXPECTED_PROTOCOL_STATUS.init(); @@ -44,7 +48,10 @@ public class AbstractTradeTest extends AbstractOfferTest { assertNotNull(trade); assertEquals(EXPECTED_PROTOCOL_STATUS.state.name(), trade.getState()); assertEquals(EXPECTED_PROTOCOL_STATUS.phase.name(), trade.getPhase()); - assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished()); + + if (!isLongRunningTest) + assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositConfirmed()); assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatSent, trade.getIsFiatSent()); assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatReceived, trade.getIsFiatReceived()); diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 3fd26f5c5c..2ae467611e 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -20,9 +20,12 @@ package bisq.apitest.method.trade; import bisq.core.payment.PaymentAccount; import bisq.proto.grpc.BtcBalanceInfo; +import bisq.proto.grpc.TradeInfo; import io.grpc.StatusRuntimeException; +import java.util.function.Predicate; + import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; @@ -39,6 +42,7 @@ import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED; import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.Trade.State.*; +import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -51,7 +55,7 @@ import static protobuf.OpenOffer.State.AVAILABLE; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class TakeBuyBTCOfferTest extends AbstractTradeTest { - // Alice is buyer, Bob is seller. + // Alice is maker/buyer, Bob is taker/seller. // Maker and Taker fees are in BSQ. private static final String TRADE_FEE_CURRENCY_CODE = "bsq"; @@ -86,24 +90,48 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { // Cache the trade id for the other tests. tradeId = trade.getTradeId(); - genBtcBlocksThenWait(1, 1000); + genBtcBlocksThenWait(1, 4000); alicesUsdOffers = aliceClient.getMyOffersSortedByDate("buy", "usd"); assertEquals(0, alicesUsdOffers.size()); - trade = bobClient.getTrade(trade.getTradeId()); - EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX) - .setPhase(DEPOSIT_PUBLISHED) - .setDepositPublished(true); - verifyExpectedProtocolStatus(trade); - logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); + if (!isLongRunningTest) { + trade = bobClient.getTrade(trade.getTradeId()); + EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX) + .setPhase(DEPOSIT_PUBLISHED) + .setDepositPublished(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); + } + + genBtcBlocksThenWait(1, 2500); + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + trade = bobClient.getTrade(trade.getTradeId()); + + if (!trade.getIsDepositConfirmed()) { + log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + trade.getShortId(), + trade.getDepositTxId(), + i); + sleep(5000); + continue; + } else { + EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) + .setPhase(DEPOSIT_CONFIRMED) + .setDepositConfirmed(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); + break; + } + } + + if (!trade.getIsDepositConfirmed()) { + fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } - genBtcBlocksThenWait(1, 1000); - trade = bobClient.getTrade(trade.getTradeId()); - EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) - .setPhase(DEPOSIT_CONFIRMED) - .setDepositConfirmed(true); - verifyExpectedProtocolStatus(trade); - logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -114,16 +142,56 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) { try { var trade = aliceClient.getTrade(tradeId); - aliceClient.confirmPaymentStarted(trade.getTradeId()); - sleep(3000); - trade = aliceClient.getTrade(tradeId); - assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); - EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) - .setPhase(FIAT_SENT) - .setFiatSent(true); - verifyExpectedProtocolStatus(trade); - logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade); + Predicate tradeStateAndPhaseCorrect = (t) -> + t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name()) + && t.getPhase().equals(DEPOSIT_CONFIRMED.name()); + + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + if (!tradeStateAndPhaseCorrect.test(trade)) { + log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.", + trade.getShortId(), + trade.getState(), + trade.getPhase()); + // fail("Bad trade state and phase."); + sleep(1000 * 10); + trade = aliceClient.getTrade(tradeId); + continue; + } else { + break; + } + } + + if (!tradeStateAndPhaseCorrect.test(trade)) { + fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment started.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } + + aliceClient.confirmPaymentStarted(trade.getTradeId()); + sleep(6000); + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + trade = aliceClient.getTrade(tradeId); + + if (!trade.getIsFiatSent()) { + log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}", + trade.getShortId(), + i); + sleep(5000); + continue; + } else { + assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) + .setPhase(FIAT_SENT) + .setFiatSent(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade); + break; + } + } } catch (StatusRuntimeException e) { fail(e); } @@ -134,6 +202,33 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { public void testBobsConfirmPaymentReceived(final TestInfo testInfo) { try { var trade = bobClient.getTrade(tradeId); + + Predicate tradeStateAndPhaseCorrect = (t) -> + t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name()) + && (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name())); + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + if (!tradeStateAndPhaseCorrect.test(trade)) { + log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.", + trade.getShortId(), + trade.getState(), + trade.getPhase()); + // fail("Bad trade state and phase."); + sleep(1000 * 10); + trade = bobClient.getTrade(tradeId); + continue; + } else { + break; + } + } + + if (!tradeStateAndPhaseCorrect.test(trade)) { + fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } + bobClient.confirmPaymentReceived(trade.getTradeId()); sleep(3000); @@ -146,6 +241,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { .setFiatReceived(true); verifyExpectedProtocolStatus(trade); logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade); + } catch (StatusRuntimeException e) { fail(e); } @@ -170,7 +266,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { verifyExpectedProtocolStatus(trade); logTrade(log, testInfo, "Alice's view after keeping funds", trade); BtcBalanceInfo currentBalance = aliceClient.getBtcBalances(); - log.debug("{} Alice's current available balance: {} BTC", + log.info("{} Alice's current available balance: {} BTC", testName(testInfo), formatSatoshis(currentBalance.getAvailableBalance())); } catch (StatusRuntimeException e) { diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java index f86f4148e8..74da2b5cde 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -20,9 +20,12 @@ package bisq.apitest.method.trade; import bisq.core.payment.PaymentAccount; import bisq.proto.grpc.BtcBalanceInfo; +import bisq.proto.grpc.TradeInfo; import io.grpc.StatusRuntimeException; +import java.util.function.Predicate; + import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; @@ -36,6 +39,7 @@ import static bisq.cli.CurrencyFormat.formatSatoshis; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.trade.Trade.Phase.*; import static bisq.core.trade.Trade.State.*; +import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -48,7 +52,7 @@ import static protobuf.OpenOffer.State.AVAILABLE; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class TakeSellBTCOfferTest extends AbstractTradeTest { - // Alice is seller, Bob is buyer. + // Alice is maker/seller, Bob is taker/buyer. // Maker and Taker fees are in BTC. private static final String TRADE_FEE_CURRENCY_CODE = "btc"; @@ -90,21 +94,44 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { var takeableUsdOffers = bobClient.getOffersSortedByDate("sell", "usd"); assertEquals(0, takeableUsdOffers.size()); - trade = bobClient.getTrade(trade.getTradeId()); - EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) - .setPhase(DEPOSIT_PUBLISHED) - .setDepositPublished(true); - verifyExpectedProtocolStatus(trade); + if (!isLongRunningTest) { + trade = bobClient.getTrade(trade.getTradeId()); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) + .setPhase(DEPOSIT_PUBLISHED) + .setDepositPublished(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); + } - logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); + genBtcBlocksThenWait(1, 2500); + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + trade = bobClient.getTrade(trade.getTradeId()); + + if (!trade.getIsDepositConfirmed()) { + log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + trade.getShortId(), + trade.getDepositTxId(), + i); + sleep(5000); + continue; + } else { + EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) + .setPhase(DEPOSIT_CONFIRMED) + .setDepositConfirmed(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); + break; + } + } + + if (!trade.getIsDepositConfirmed()) { + fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } - genBtcBlocksThenWait(1, 1000); - trade = bobClient.getTrade(trade.getTradeId()); - EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) - .setPhase(DEPOSIT_CONFIRMED) - .setDepositConfirmed(true); - verifyExpectedProtocolStatus(trade); - logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -115,17 +142,54 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { public void testBobsConfirmPaymentStarted(final TestInfo testInfo) { try { var trade = bobClient.getTrade(tradeId); - bobClient.confirmPaymentStarted(tradeId); - sleep(3000); - trade = bobClient.getTrade(tradeId); - // Note: offer.state == available - assertEquals(AVAILABLE.name(), trade.getOffer().getState()); - EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) - .setPhase(FIAT_SENT) - .setFiatSent(true); - verifyExpectedProtocolStatus(trade); - logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade); + Predicate tradeStateAndPhaseCorrect = (t) -> + t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_CONFIRMED.name()); + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + if (!tradeStateAndPhaseCorrect.test(trade)) { + log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.", + trade.getShortId(), + trade.getState(), + trade.getPhase()); + // fail("Bad trade state and phase."); + sleep(1000 * 10); + trade = bobClient.getTrade(tradeId); + continue; + } else { + break; + } + } + + if (!tradeStateAndPhaseCorrect.test(trade)) { + fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not confirm payment started.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } + + bobClient.confirmPaymentStarted(tradeId); + sleep(6000); + + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + trade = bobClient.getTrade(tradeId); + + if (!trade.getIsFiatSent()) { + log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}", + trade.getShortId(), + i); + sleep(5000); + continue; + } else { + // Note: offer.state == available + assertEquals(AVAILABLE.name(), trade.getOffer().getState()); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) + .setPhase(FIAT_SENT) + .setFiatSent(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade); + break; + } + } } catch (StatusRuntimeException e) { fail(e); } @@ -136,6 +200,32 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) { try { var trade = aliceClient.getTrade(tradeId); + + Predicate tradeStateAndPhaseCorrect = (t) -> + t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name()) + && (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name())); + for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { + if (!tradeStateAndPhaseCorrect.test(trade)) { + log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.", + trade.getShortId(), + trade.getState(), + trade.getPhase()); + // fail("Bad trade state and phase."); + sleep(1000 * 10); + trade = aliceClient.getTrade(tradeId); + continue; + } else { + break; + } + } + + if (!tradeStateAndPhaseCorrect.test(trade)) { + fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment received.", + trade.getShortId(), + trade.getState(), + trade.getPhase())); + } + aliceClient.confirmPaymentReceived(trade.getTradeId()); sleep(3000); @@ -173,7 +263,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { verifyExpectedProtocolStatus(trade); logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade); BtcBalanceInfo currentBalance = bobClient.getBtcBalances(); - log.debug("{} Bob's current available balance: {} BTC", + log.info("{} Bob's current available balance: {} BTC", testName(testInfo), formatSatoshis(currentBalance.getAvailableBalance())); } catch (StatusRuntimeException e) { diff --git a/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java new file mode 100644 index 0000000000..337f2dc160 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java @@ -0,0 +1,100 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.apitest.scenario; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledIf; + +import static java.lang.System.getenv; + + + +import bisq.apitest.method.trade.AbstractTradeTest; +import bisq.apitest.method.trade.TakeBuyBTCOfferTest; +import bisq.apitest.method.trade.TakeSellBTCOfferTest; + +@EnabledIf("envLongRunningTestEnabled") +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class LongRunningTradesTest extends AbstractTradeTest { + + @Test + @Order(1) + public void testTradeLoop(final TestInfo testInfo) { + int numTrades = 0; + while (numTrades < 50) { + + log.info("*******************************************************************"); + log.info("Trade # {}", ++numTrades); + log.info("*******************************************************************"); + + EXPECTED_PROTOCOL_STATUS.init(); + testTakeBuyBTCOffer(testInfo); + + genBtcBlocksThenWait(1, 1000 * 15); + + log.info("*******************************************************************"); + log.info("Trade # {}", ++numTrades); + log.info("*******************************************************************"); + + EXPECTED_PROTOCOL_STATUS.init(); + testTakeSellBTCOffer(testInfo); + + genBtcBlocksThenWait(1, 1000 * 15); + } + } + + public void testTakeBuyBTCOffer(final TestInfo testInfo) { + TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest(); + setLongRunningTest(true); + test.testTakeAlicesBuyOffer(testInfo); + test.testAlicesConfirmPaymentStarted(testInfo); + test.testBobsConfirmPaymentReceived(testInfo); + test.testAlicesKeepFunds(testInfo); + } + + public void testTakeSellBTCOffer(final TestInfo testInfo) { + TakeSellBTCOfferTest test = new TakeSellBTCOfferTest(); + setLongRunningTest(true); + test.testTakeAlicesSellOffer(testInfo); + test.testBobsConfirmPaymentStarted(testInfo); + test.testAlicesConfirmPaymentReceived(testInfo); + test.testBobsBtcWithdrawalToExternalAddress(testInfo); + } + + protected static boolean envLongRunningTestEnabled() { + String envName = "LONG_RUNNING_TRADES_TEST_ENABLED"; + String envX = getenv(envName); + if (envX != null) { + log.info("Enabled, found {}.", envName); + return true; + } else { + log.info("Skipped, no environment variable {} defined.", envName); + log.info("To enable on Mac OS or Linux:" + + "\tIf running in terminal, export LONG_RUNNING_TRADES_TEST_ENABLED=true in bash shell." + + "\tIf running in Intellij, set LONG_RUNNING_TRADES_TEST_ENABLED=true in launcher's Environment variables field."); + return false; + } + } +}