Ejemplo n.º 1
0
def test_syncing_same_file_again():
    """
    If the same file is synced with no changes, then Lean won't send the same sort of responses.
    """

    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=1),
        LeanSendsResponse({
            "message": "file invalidated",
            "response": "ok",
            "seq_num": 1
        }),
        LeanTakesTime(.01),
        LeanSendsResponse({
            "is_running": False,
            "response": "current_tasks",
            "tasks": []
        }),

        # sync same file again which hasn't changed.  Lean WON'T send a current_tasks response
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=2),
        LeanSendsResponse({
            "message": "file unchanged",
            "response": "ok",
            "seq_num": 2
        }),
        LeanTakesTime(.01),

        # The python-lean interface should not block and instead send an info request right away
        LeanShouldGetRequest(InfoRequest(file_name="test.lean",
                                         line=1,
                                         column=0),
                             seq_num=3),
        LeanSendsResponse({
            "response": "ok",
            "seq_num": 3
        }),
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync("test.lean")
            await server.full_sync("test.lean")  # sync same file twice

            await server.state(filename="test.lean", line=1, col=0)

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
Ejemplo n.º 2
0
def test_error_in_sync():
    """
    If there is an error in syncing (such as the file not existing), then one shouldn't wait
    for a current_tasks response.
    """

    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(file_name="bad_file_name"),
                             seq_num=1),
        LeanSendsResponse({
            "message": "file 'bad_file_name' not found in the LEAN_PATH",
            "response": "error",
            "seq_num": 1
        }),
        LeanTakesTime(.01),

        # the lean process should throw an error (which will be caught and handled)

        # If this part fails, that means the interface blocked waiting for a current_tasks response
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=2),
        LeanSendsResponse({
            "message": "file unchanged",
            "response": "ok",
            "seq_num": 2
        }),
        LeanTakesTime(.01),
        LeanSendsResponse({
            "is_running": False,
            "response": "current_tasks",
            "tasks": []
        }),
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery)
            await start_with_mock_lean(server, mock_lean_script)

            # for this test to pass it has to (1) not block forever and (2) throw an error
            try:
                await server.full_sync("bad_file_name")
                assert False, "An error should have been thrown here"
            except ChildProcessError:
                pass

            await server.full_sync("test.lean")  # sync a different file

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
    async def full_sync(self, filename, content=None) -> None:
        """Fully compile a Lean file before returning."""
        # Waiting for the response is not enough, so we prepare another event
        response = await self.send(SyncRequest(filename, content))
        assert isinstance(response, SyncResponse)

        if response.message == "file invalidated":
            self.is_fully_ready = trio.Event()
            await self.is_fully_ready.wait()
def test_normal_commands_wait_for_and_return_response():
    """
    Most commands, like the info command should wait for a response.
    """
    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=1),
        LeanSendsResponse({"message": "file invalidated", "response": "ok", "seq_num": 1}),
        LeanTakesTime(.01),
        LeanSendsResponse({"is_running": False, "response": "current_tasks", "tasks": []}),

        # info request 1
        LeanShouldGetRequest(InfoRequest(file_name="test.lean", line=1, column=25), seq_num=2),
        LeanShouldNotGetRequest(),  # waiting for a lean response
        LeanSendsResponse({
            "record": {
                "full-id": "max",
                "source": {
                    "column": 11,
                    "file": "path/lib/lean/library/init/algebra/functions.lean",
                    "line": 12
                },
                "type": "\xce\xa0 {\xce\xb1 : Type u} [_inst_1 : decidable_linear_order \xce\xb1], \xce\xb1 \xe2\x86\x92 \xce\xb1 \xe2\x86\x92 \xce\xb1"
            },
            "response": "ok",
            "seq_num": 2
        }),

        LeanTakesTime(.01),

        # info request 2
        LeanShouldGetRequest(InfoRequest(file_name="test.lean", line=100, column=0), seq_num=3),
        LeanSendsResponse({"response": "ok", "seq_num": 3}),
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery, debug_bytes=True)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync('test.lean')

            # check response object is parsed and of the correct type
            # (more parsing tests can be found in unit tests for the commands.py file)
            response1 = await server.send(InfoRequest(file_name="test.lean", line=1, column=25))
            assert isinstance(response1, InfoResponse)
            assert response1.record.source.column == 11

            response2 = await server.send(InfoRequest(file_name="test.lean", line=100, column=0))
            assert isinstance(response2, InfoResponse)
            assert response2.record is None

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
def test_sleep_commands_do_not_wait_for_response():
    """
    The sleep commands should not wait for a response.
    """

    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=1),
        LeanSendsResponse({"message": "file invalidated", "response": "ok", "seq_num": 1}),
        LeanTakesTime(.01),
        LeanSendsResponse({"is_running": False, "response": "current_tasks", "tasks": []}),

        # Sleep request
        LeanShouldGetRequest(SleepRequest(), seq_num=2),

        # An info request sent during the sleep request.
        # The server should wait for a response from the sleep request
        LeanShouldGetRequest(InfoRequest(file_name="test.lean", line=1, column=0), seq_num=3),
        LeanTakesTime(.01),
        LeanSendsResponse({"response": "ok", "seq_num": 3}),

        # Long sleep request
        LeanShouldGetRequest(LongSleepRequest(), seq_num=4),
        # Lean does not wait for a response and the trio server shouldn't block

        # An info request sent during the sleep request.
        # The server should wait for a response from the sleep request
        LeanShouldGetRequest(InfoRequest(file_name="test.lean", line=1, column=0), seq_num=5),
        LeanTakesTime(.01),
        LeanSendsResponse({"response": "ok", "seq_num": 5}),
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery, debug_bytes=True)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync('test.lean')

            response1 = await server.send(SleepRequest())
            assert response1 is None

            response2 = await server.send(InfoRequest(file_name="test.lean", line=1, column=0))
            assert isinstance(response2, InfoResponse)

            response3 = await server.send(LongSleepRequest())
            assert response3 is None

            response4 = await server.send(InfoRequest(file_name="test.lean", line=1, column=0))
            assert isinstance(response4, InfoResponse)

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
Ejemplo n.º 6
0
def test_full_sync_waits_until_lean_ready():
    """
    Check that full_sync waits until the Lean server is ready.
    In particular, it must first get a "file invalidated" response, and then an "all_tasks" response
    before continuing.
    """
    mock_lean_script = [
        LeanShouldGetRequest(SyncRequest(file_name="test.lean", content="--"),
                             seq_num=1),

        # current_tasks response is sent BEFORE the ok response
        LeanSendsResponse({
            "is_running": False,
            "response": "current_tasks",
            "tasks": []
        }),
        LeanSendsResponse({
            "message": "file invalidated",
            "response": "ok",
            "seq_num": 1
        }),

        # shouldn't be receiving anything yet
        LeanShouldNotGetRequest(),
        LeanSendsResponse({
            "is_running": False,
            "response": "current_tasks",
            "tasks": []
        }),

        # now it is ok to get a new request
        LeanShouldGetRequest(InfoRequest(file_name="test.lean",
                                         line=1,
                                         column=0),
                             seq_num=2),
        LeanSendsResponse({
            "response": "ok",
            "seq_num": 2
        }),
    ]

    async def check_waiting_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync('test.lean', content='--')
            await server.state(filename="test.lean", line=1, col=0)

            nursery.cancel_scope.cancel()

    trio.run(check_waiting_behavior)
Ejemplo n.º 7
0
def test_receiver_processes_only_whole_messages():
    """
    Lean sometimes will not send a complet message over at a time.  It may even break up a unicode character
    like "⊢" (three bytes).

    This tests that the message is properly received.
    """

    expected_state = "⊢ true"  # In bytes this is b'\xe2\x8a\xa2 true'

    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(
            file_name="test.lean", content="example : true := \nbegin end"),
                             seq_num=1),
        LeanSendsResponse({
            "message": "file invalidated",
            "response": "ok",
            "seq_num": 1
        }),
        LeanTakesTime(.01),
        LeanSendsResponse({
            "is_running": False,
            "response": "current_tasks",
            "tasks": []
        }),
        LeanShouldGetRequest(InfoRequest(file_name="test.lean",
                                         line=2,
                                         column=0),
                             seq_num=2),

        # response sent in two chunks over the stream splitting the "⊢" character b'\xe2\x8a\xa2' in half.
        LeanSendsBytes(b'{"record":{"state":"\xe2\x8a'),
        LeanTakesTime(.01),
        LeanSendsBytes(b'\xa2 true"},"response":"ok","seq_num":2}\n'),
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery, debug_bytes=True)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync('test.lean',
                                   content='example : true := \nbegin end')
            state = await server.state(filename="test.lean", line=2, col=0)

            assert state == "⊢ true"

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
def test_errors_are_handled():
    """
    If a Lean server returns an error (specifically the "response" field is "error",
    then the Trio server should also raise an error.
    """

    mock_lean_script = [
        # initial sync
        LeanShouldGetRequest(SyncRequest(file_name="test.lean"), seq_num=1),
        LeanSendsResponse({"message": "file invalidated", "response": "ok", "seq_num": 1}),
        LeanTakesTime(.01),
        LeanSendsResponse({"is_running": False, "response": "current_tasks", "tasks": []}),

        # An info request sent during the sleep request.
        # The server should wait for a response from the sleep request
        LeanShouldGetRequest(InfoRequest(file_name="wrongfile.lean", line=1, column=0), seq_num=2),
        LeanTakesTime(.01),
        LeanSendsResponse({
            "message": "file \'wrongfile.lean\' not found in the LEAN_PATH",
            "response": "error",
            "seq_num": 2
        })
    ]

    async def check_behavior():
        async with trio.open_nursery() as nursery:
            server = TrioLeanServer(nursery, debug_bytes=True)
            await start_with_mock_lean(server, mock_lean_script)

            await server.full_sync('test.lean')

            # for this test to pass it has to throw an error
            try:
                await server.send(InfoRequest(file_name="wrongfile.lean", line=1, column=0))
                assert False, "An error should have been thrown here"
            except ChildProcessError:
                pass

            nursery.cancel_scope.cancel()

    trio.run(check_behavior)
Ejemplo n.º 9
0
 def sync(self, file_name, content=None):
     """Send synchronisation query to Lean."""
     self.send(SyncRequest(file_name, content))
     self.is_busy = True
 async def full_sync(self, filename, content=None) -> None:
     """Fully compile a Lean file before returning."""
     # Waiting for the response is not enough, so we prepare another event
     await self.send(SyncRequest(filename, content))
     self.is_fully_ready = trio.Event()
     await self.is_fully_ready.wait()