From e511f84e56cbf055839e3b1712248fcac15c2c2b Mon Sep 17 00:00:00 2001 From: LingKa Date: Thu, 8 Feb 2024 01:03:40 +0800 Subject: [PATCH] test: add unary clinet test samples Signed-off-by: LingKa --- .github/workflows/ci.yml | 51 ++++++++- src/curp/curp_test.py | 237 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/curp/curp_test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f957f7..68d9260 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,56 @@ jobs: files: ./coverage.xml fail_ci_if_error: true verbose: true - + + mock: + name: Test Mock Sample Validation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + path: py-xline + + - name: Checkout mock xline + uses: actions/checkout@v4 + with: + repository: xline-kv/mock-xline + path: mock-xline + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Pip + run: sudo apt install pip + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: 1.21 + + - name: Generate API + run: | + cd py-xline + python3 -m pip install grpcio grpcio-tools + git submodule init + git submodule update + make + + - name: Install Hatch + run: pip install hatch==1.7.0 + + - name: Start the cluster + run: | + cd mock-xline + ./scripts/quick_start.sh + + - name: Run tests + run: | + cd py-xline + hatch run test ./src/curp/ -v + commit: name: Commit Message Validation runs-on: ubuntu-latest diff --git a/src/curp/curp_test.py b/src/curp/curp_test.py new file mode 100644 index 0000000..cbc1142 --- /dev/null +++ b/src/curp/curp_test.py @@ -0,0 +1,237 @@ +""" +Test for curp client +""" + +import pytest +from src.curp.unary import UnaryBuilder, UnaryConfig +from src.rpc.type import CurpError +from curp_command_pb2 import ProposeId +from api.xline.xline_command_pb2 import Command, CommandResponse, SyncResponse +from api.xline.rpc_pb2 import RangeResponse + + +@pytest.mark.asyncio +async def test_unary_fetch_clusters(): + """ + Test unary fetch clusters + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + res = await unary.fetch_cluster() + assert len(res.members) == 5 + assert res.members[0].addrs == ["A0"] + assert res.members[1].addrs == ["A1"] + assert res.members[2].addrs == ["A2"] + assert res.members[3].addrs == ["A3"] + assert res.members[4].addrs == ["A4"] + + +@pytest.mark.asyncio +async def test_unary_fetch_clusters_failed(): + """ + Test unary fetch clusters failed + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + try: + await unary.fetch_cluster() + except CurpError as e: + assert isinstance(e, CurpError) + + +@pytest.mark.asyncio +async def test_fast_round_works(): + """ + Test fast round works + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + er, err = await unary.fast_round(ProposeId(seq_num=1), Command()) + assert er is not None + assert er == CommandResponse(range_response=RangeResponse(count=1)) + assert err is None + +@pytest.mark.asyncio +async def test_fast_round_return_early_err(): + """ + Test fast round return early error + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + try: + await unary.fast_round(ProposeId(seq_num=2), Command()) + except CurpError as e: + assert isinstance(e, CurpError) + +@pytest.mark.asyncio +async def test_fast_round_less_quorum(): + """ + Test fast round less quorum + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + try: + await unary.fast_round(ProposeId(seq_num=3), Command()) + except CurpError as e: + assert isinstance(e, CurpError) + +@pytest.mark.asyncio +async def test_fast_round_with_two_leader(): + """ + Test fast round with two leader + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + try: + await unary.fast_round(ProposeId(seq_num=4), Command()) + except RuntimeError as e: + assert isinstance(e, RuntimeError) + +@pytest.mark.asyncio +async def test_fast_round_without_leader(): + """ + Test fast round without leader + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + try: + await unary.fast_round(ProposeId(seq_num=5), Command()) + except RuntimeError as e: + assert isinstance(e, RuntimeError) + + +@pytest.mark.asyncio +async def test_unary_slow_round_fetch_leader_first(): + """ + Test unary slow round fetch leader first + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).build() + (er, asr), _ = await unary.slow_round(ProposeId(seq_num=6)) + assert er == CommandResponse(range_response=RangeResponse(count=1)) + assert asr == SyncResponse(revision=1) + + +@pytest.mark.asyncio +async def test_unary_propose_fast_path_works(): + """ + Test unary propose fast path works + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + + unary = UnaryBuilder(all_members, config).set_leader_state(0, 1).build() + er, asr = await unary.repeatable_propose(ProposeId(seq_num=7), Command()) + assert er == CommandResponse(range_response=RangeResponse(count=1)) + assert asr is None + +@pytest.mark.asyncio +async def test_unary_propose_slow_path_works(): + """ + Test unary propose slow path works + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).set_leader_state(0, 1).build() + er, asr = await unary.repeatable_propose(ProposeId(seq_num=7), Command(), use_fast_path=False) + assert er == CommandResponse(range_response=RangeResponse(count=1)) + assert asr == SyncResponse(revision=1) + + +@pytest.mark.asyncio +async def test_unary_propose_fast_path_fallback_slow_path(): + """ + Test unary propose fast path fallback slow path + """ + all_members = { + 0: ["127.0.0.1:48081"], + 1: ["127.0.0.1:48082"], + 2: ["127.0.0.1:48083"], + 3: ["127.0.0.1:48084"], + 4: ["127.0.0.1:48085"], + } + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).set_leader_state(0, 1).build() + er, asr = await unary.repeatable_propose(ProposeId(seq_num=8), Command()) + assert er == CommandResponse(range_response=RangeResponse(count=1)) + assert asr == SyncResponse(revision=1) + + +@pytest.mark.asyncio +async def test_unary_propose_return_early_err(): + """ + Test unary propose return early error + """ + all_members = {0: ["127.0.0.1:48081"]} + config = UnaryConfig(1, 2) + unary = UnaryBuilder(all_members, config).set_leader_state(0, 1).build() + try: + await unary.repeatable_propose(ProposeId(seq_num=9), Command()) + except CurpError as e: + assert isinstance(e, CurpError)