import shutil
from pathlib import Path
from typing import Dict, List, Sequence

import pytest
from clarabot_nano_common.utility.path.common import normalize_absolute_path

from clarabot_nano_tests.indexer.scheduler.utility import SchedulerTesterBase, PathStructureBuilder
from clarabot_nano_tests.testing.file import temp_path


def _assert_accessors(ps, r, x: Dict[str, Sequence[List[str]]]):
    for real_real, args in x.items():
        roots = {r}
        if len(args) == 2:
            positive, negative = args
        elif len(args) == 3:
            positive, negative, tmp = args
            roots.update(tmp)
        else:
            assert False, str(args)
        doc_path = Path(r, real_real).as_posix()
        assert doc_path in ps, f"{r}\n{real_real}\n{doc_path}\n{ps}"
        accs = ps[doc_path]["accessors"]
        if positive:
            assert all(any(p.endswith(ap) for p in accs) for ap in positive), f"{real_real}\n{accs}\n{positive}"
        if negative:
            assert all(not any(p.endswith(ap) for p in accs) for ap in negative), f"{real_real}\n{accs}\n{negative}"
        acc_roots = set(p.rpartition("//")[0] for p in accs)
        assert acc_roots == roots


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_0(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Simple incremental changes and their indexing.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]

    # Initial indexing of content
    psb.create_files_in_dirs(r, {"": 8, "a": 8, "b": 8, "c/c1": 8, "x/.~CbN.ősi#/x": 2})
    psb.create_dirs(r, ["c/c2", "d/d1"])
    # insert a "stuck" upload temporary file to check if it is skipped by the scheduler
    Path(r, ".~CbN.ősi.txt#").write_bytes(b"writing, but stalled 0...")
    Path(r, ".~CbN..#").write_bytes(b"writing, but stalled 1...")
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(not p.endswith(".~CbN.ősi.txt#") and not p.endswith(".~CbN..#") for p in ps.keys())
    assert any(p.find(".~CbN.ősi#") >= 0 for p in ps.keys())

    # Add some new dirs and files
    psb.create_file(r, "e/e1")
    psb.create_files_in_dirs(r, {"": 2, "a/a1": 4, "a/a2": 4, "b": 8, "c/c1/c12": 4, "d/d2": 8})
    psb.create_dirs(r, ["c/c1/c11", "d/d3"])
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # Remove some dirs and files (no lost dir case)
    shutil.rmtree(Path(r, "c"))
    psb.delete_files_in_dirs(r, {"": 4, "a": 3, "a/a1": 1, "b": 10, "d/d2": 3})
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # Turn a dir into a file and vice-versa.
    Path(r, "e/e1").unlink()
    psb.create_files_in_dirs(r, {"e/e1": 7})
    shutil.rmtree(Path(r, "d/d2"))
    psb.create_file(r, "d/d2")
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # Update some files
    psb.update_files_in_dirs(r, {"": 2, "a/a1": 2, "b": 2, "d": 1, "e/e1": 2})
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # Delete all files in a directory to get deleted by the "lost"-branch
    with psb.keep_paths_from_lost(r, ["e/e1"]):
        psb.delete_files_in_dirs(r, {"e/e1": 7})
        await indexer_scheduler_tester.run_check_repeated_polls(
            psb, repeat=indexer_scheduler_tester.ec.indexer_remove_lost_count
        )
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # check usage of soft commits (triggered by simulated interrupts)
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert (x := sum(sc) / len(sc)) > 4, f"{x}\n{sc}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_1(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Test commit tracking in optimal case. (no explicit commit)
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]
    e = indexer_scheduler_tester.new_scheduler_engine(ignore_add_commit_time=True)

    psb.create_files_in_dirs(
        r,
        {
            "": 8,
            "a": 8,
            "a/re0": 8,
            "a/rx1": 8,
            "a/ra2": 8,
            "a/ra2/tz6": 8,
            "a/ra2/jki": 8,
            "a/ra2/wer": 8,
            "a/qrkf/fee/rdf": 8,
            "a/qrkf/fee/mnbh": 8,
            "a/qrkf/fee/esf": 8,
            "a/qrkf/ess/fdsv": 8,
            "a/qrkf/ess/fdsv/feee": 8,
            "b": 8,
            "c/c1": 8,
        },
    )
    psb.create_dirs(r, ["a/qrkf/fee/asd", "a/qrkf/fessw", "c/c2", "d/d1"])
    await e.run_single_poll_cycle(run_auto_soft_commits=False, simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"

    # some updates and deletes
    psb.create_files_in_dirs(r, {"a/rx1": 1, "a/ra2": 1, "a/ra2/tz6": 1, "a/qrkf/ess/fdsv/feee/x": 2})
    psb.update_files_in_dirs(r, {"a/rx1": 3, "a/ra2": 3, "a/ra2/tz6": 3})
    shutil.rmtree(Path(r, "a/qrkf/ess"))
    psb.delete_files_in_dirs(r, {"": 3, "a": 3, "a/ra2/jki": 3, "b": 3})
    psb.create_dirs(r, ["a/ra2/kkrb"])
    await e.run_single_poll_cycle(run_auto_soft_commits=False, simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_2(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Simple symlinks. (no chains, cycles, escapes)
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]

    # Initial indexing of content
    psb.create_files_in_dirs(r, {"": 8, "a": 8, "b": 8})
    psb.create_file(r, "a/xy")
    Path(r, "as").symlink_to(Path(r, "a"), target_is_directory=True)
    Path(r, "as2").symlink_to(Path(r, "a"), target_is_directory=True)
    Path(r, "bs").symlink_to(Path(r, "b"), target_is_directory=True)
    Path(r, "xys").symlink_to(Path(r, "a/xy"))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(
        ps,
        r,
        {
            "a": (["//a", "//as", "//as2"], []),
            "b": (["//b", "//bs"], []),
            "a/xy": (["//a/xy", "//xys"], []),
        },
    )

    # Make changes 1
    Path(r, "as2").unlink()
    Path(r, "bs2").symlink_to(Path(r, "b"), target_is_directory=True)
    psb.update_files_in_dirs(r, {"a": 2, "b": 2})
    psb.create_files_in_dirs(r, {"": 1, "a": 1, "b": 1})
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(
        ps,
        r,
        {
            "a": (["//a", "//as"], ["//as2"]),
            "b": (["//b", "//bs", "//bs2"], []),
            "a/xy": (["//a/xy", "//xys"], []),
        },
    )

    # Make changes 2
    psb.delete_files_in_dirs(r, {"": 3, "a": 3, "b": 3})
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # check usage of soft commits
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert 3.5 < (x := sum(sc) / len(sc)) < 20.0, f"{x}\n{sc}"  # high limit may be reached here occasionally


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_3(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Chained symlinks.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]

    # Initial indexing of content
    fs = psb.create_files_in_dirs(r, {"": 4, "a": 4, "b": 4, "c": 4, "d": 4, "e": 4, "f": 4})
    Path(r, "a/bs").symlink_to(Path(r, "b"), target_is_directory=True)
    Path(r, "b/cs").symlink_to(Path(r, "c"), target_is_directory=True)
    Path(r, "c/ds").symlink_to(Path(r, "d"), target_is_directory=True)
    Path(r, "d/es").symlink_to(Path(r, "e"), target_is_directory=True)
    Path(r, "e/fs").symlink_to(Path(r, "f"), target_is_directory=True)
    Path(r, "a/bf0s").symlink_to(Path(r, "b", fs["b"][0]))
    Path(r, "b/cf0s").symlink_to(Path(r, "c", fs["c"][0]))
    Path(r, "c/df0s").symlink_to(Path(r, "d", fs["d"][0]))
    Path(r, "d/ef0s").symlink_to(Path(r, "e", fs["e"][0]))
    Path(r, "e/ff0s").symlink_to(Path(r, "f", fs["f"][0]))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    accs_check_full = {
        "a": ([], []),
        "b": (["//a/bs"], []),
        "c": (["//a/bs/cs", "//b/cs"], []),
        "d": (["//a/bs/cs/ds", "//c/ds"], []),
        "e": (["//a/bs/cs/ds/es", "//d/es"], []),
        "f": (["//a/bs/cs/ds/es/fs", "//e/fs"], []),
    }
    _assert_accessors(ps, r, accs_check_full)

    # break chain
    Path(r, "c/ds").unlink()
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    accs_check_no_ds = {
        "a": ([], []),
        "b": (["//a/bs"], []),
        "c": (["//a/bs/cs", "//b/cs"], []),
        "d": ([], ["//a/bs/cs/ds", "//c/ds"]),
        "e": (["//d/es"], ["//a/bs/cs/ds/es"]),
        "f": (["//d/es/fs", "//e/fs"], ["//a/bs/cs/ds/es/fs"]),
    }
    _assert_accessors(ps, r, accs_check_no_ds)

    # restore chain
    Path(r, "c/ds").symlink_to(Path(r, "d"), target_is_directory=True)
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(ps, r, accs_check_full)

    # double-break chain
    Path(r, "b/cs").unlink()
    Path(r, "d/es").unlink()
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    accs_check_no_cs_es = {
        "a": ([], []),
        "b": (["//a/bs"], []),
        "c": ([], ["//a/bs/cs", "//b/cs"]),
        "d": (["//c/ds"], ["//a/bs/cs/ds"]),
        "e": ([], ["//a/bs/cs/ds/es", "//d/es"]),
        "f": (["//e/fs"], ["//a/bs/cs/ds/es/fs"]),
    }
    _assert_accessors(ps, r, accs_check_no_cs_es)

    # restore chain
    Path(r, "b/cs").symlink_to(Path(r, "c"), target_is_directory=True)
    Path(r, "d/es").symlink_to(Path(r, "e"), target_is_directory=True)
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(ps, r, accs_check_full)

    # break chain by dir removal
    shutil.rmtree(Path(r, "c"))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert Path(r, "c").as_posix() not in ps
    accs_check_no_c = {
        "a": ([], []),
        "b": (["//a/bs"], []),
        "d": ([], ["//a/bs/cs/ds", "//c/ds"]),
        "e": (["//d/es"], ["//a/bs/cs/ds/es"]),
        "f": (["//d/es/fs", "//e/fs"], ["//a/bs/cs/ds/es/fs"]),
        f"f/{fs['f'][1]}": ([], ["//a/ff1s"]),
    }
    _assert_accessors(ps, r, accs_check_no_c)

    # multi-add chains
    Path(r, "a/ds").symlink_to(Path(r, "d"), target_is_directory=True)
    Path(r, "a/es").symlink_to(Path(r, "e"), target_is_directory=True)
    Path(r, "b/ds").symlink_to(Path(r, "d"), target_is_directory=True)
    Path(r, "a/bf1s").symlink_to(Path(r, "b", fs["b"][1]))
    Path(r, "a/cf1s").symlink_to(Path(r, "c", fs["c"][1]))
    Path(r, "a/df1s").symlink_to(Path(r, "d", fs["d"][1]))
    Path(r, "a/ef1s").symlink_to(Path(r, "e", fs["e"][1]))
    Path(r, "a/ff1s").symlink_to(Path(r, "f", fs["f"][1]))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert Path(r, "c").as_posix() not in ps
    accs_check_no_c = {
        "a": ([], []),
        "b": (["//a/bs"], []),
        "d": (["//a/ds", "//b/ds"], ["//a/bs/cs/ds", "//c/ds"]),
        "e": (["//a/es", "//b/ds/es", "//a/ds/es", "//d/es"], ["//a/bs/cs/ds/es"]),
        "f": (["//a/es/fs", "//b/ds/es/fs", "//a/ds/es/fs", "//d/es/fs", "//e/fs"], ["//a/bs/cs/ds/es/fs"]),
        f"f/{fs['f'][1]}": (["//a/ff1s"], []),
    }
    _assert_accessors(ps, r, accs_check_no_c)

    # check usage of soft commits
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert 11 < (x := sum(sc) / len(sc)) < 17, f"{x}\n{sc}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_4(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Cycled symlinks.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]

    # Initial indexing of content
    fs = psb.create_files_in_dirs(r, {"": 4, "a": 4, "b": 4, "c": 4})
    Path(r, "a/bs").symlink_to(Path(r, "b"), target_is_directory=True)
    Path(r, "b/cs").symlink_to(Path(r, "c"), target_is_directory=True)
    Path(r, "c/as").symlink_to(Path(r, "a"), target_is_directory=True)
    Path(r, "a/cf0s").symlink_to(Path(r, "c", fs["c"][0]))
    Path(r, "c/bf0s").symlink_to(Path(r, "b", fs["b"][0]))
    Path(r, "b/af0s").symlink_to(Path(r, "a", fs["a"][0]))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    accs_check_full = {
        "a": (["//a/bs/cs/as", "//b/cs/as", "//c/as"], ["//a/bs/cs/as/bs"]),
        "b": (["//b/cs/as/bs", "//c/as/bs", "//a/bs"], ["//b/cs/as/bs/cs"]),
        "c": (["//c/as/bs/cs", "//a/bs/cs", "//b/cs"], ["//c/as/bs/cs/as"]),
    }
    _assert_accessors(ps, r, accs_check_full)

    # break cycle
    Path(r, "b/cs").unlink()
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(
        ps,
        r,
        {
            "a": (["//c/as"], ["//a/bs/cs/as", "//b/cs/as", "//a/bs/cs/as/bs"]),
            "b": (["//c/as/bs", "//a/bs"], ["//b/cs/as/bs", "//b/cs/as/bs/cs"]),
            "c": ([], ["//c/as/bs/cs", "//a/bs/cs", "//b/cs", "//c/as/bs/cs/as"]),
        },
    )

    # restore cycle
    Path(r, "b/cs").symlink_to(Path(r, "c"), target_is_directory=True)
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    _assert_accessors(ps, r, accs_check_full)

    # check usage of soft commits
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert 2 < (x := sum(sc) / len(sc)) < 13, f"{x}\n{sc}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_5(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Escaping symlinks.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]

    # outside struct
    ro = Path(normalize_absolute_path(temp_path(tmpdir))).as_posix()
    fso = psb.create_files_in_dirs(ro, {"": 4, "j": 4, "k": 4, "l": 4})

    # Initial indexing of content
    fs = psb.create_files_in_dirs(r, {"": 4, "a": 4, "b": 4, "c": 4})
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # test escaping symlinks being ignored
    Path(r, "rs").symlink_to(Path(ro), target_is_directory=True)
    Path(r, "js").symlink_to(Path(ro, "j"), target_is_directory=True)
    Path(r, "a/bs").symlink_to(Path(ro, "b"), target_is_directory=True)
    Path(r, "kf0s").symlink_to(Path(r, "k", fso["k"][0]))
    Path(r, "b/lf0s").symlink_to(Path(r, "l", fso["l"][0]))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(not p.startswith(ro) for p in ps.keys())

    # test returning symlink chain working
    Path(ro, "retb").symlink_to(Path(r, "b"), target_is_directory=True)
    Path(ro, "retbf").symlink_to(Path(r, "c", fs["c"][0]))
    Path(r, "a/escd").symlink_to(Path(ro, "retb"), target_is_directory=True)
    Path(r, "a/escf").symlink_to(Path(ro, "retbf"))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(not p.startswith(ro) for p in ps.keys())
    re_dir_b = ps[Path(r, "b").as_posix()]
    re_file_b = ps[Path(r, "b", fs["b"][0]).as_posix()]
    re_file_c = ps[Path(r, "c", fs["c"][0]).as_posix()]
    assert any(p == f"{r}//a/escd" for p in re_dir_b["accessors"]), f"{re_dir_b}\n{r}\n{ro}"
    assert any(p == f"{r}//a/escd/{fs['b'][0]}" for p in re_file_b["accessors"]), f"{re_file_b}\n{r}\n{ro}"
    assert any(p == f"{r}//a/escf" for p in re_file_c["accessors"]), f"{re_file_c}\n{r}\n{ro}"

    # check usage of soft commits
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert 1.5 < (x := sum(sc) / len(sc)) < 8.5, f"{x}\n{sc}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_6(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Test commit tracking in explicit case.
    Alternate names by symlink won't trigger commits without posix-path tracking.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 1)
    psb = PathStructureBuilder(rs)
    r = rs[0]
    e = indexer_scheduler_tester.new_scheduler_engine()

    fs = psb.create_files_in_dirs(r, {"": 8, "a": 8, "b": 8, "c": 8})
    psb.create_dirs(r, ["d"])
    Path(r, "as2").symlink_to(Path(r, "a"), target_is_directory=True)
    Path(r, "as3").symlink_to(Path(r, "a"), target_is_directory=True)
    await e.run_single_poll_cycle(simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"

    Path(r, "a", fs["a"][0]).unlink()
    Path(r, "as2").unlink()
    Path(r, "as3").unlink()
    await e.run_single_poll_cycle(simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"

    Path(r, "as5").symlink_to(Path(r, "a"), target_is_directory=True)
    await e.run_single_poll_cycle(simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"

    Path(r, "as1").symlink_to(Path(r, "a"), target_is_directory=True)
    Path(r, "as5").unlink()
    await e.run_single_poll_cycle(simulate_interruptions=False)
    await indexer_scheduler_tester.assert_from_paths(psb.generate_paths_structure())
    assert e.soft_commit_count == 1, f"{e.soft_commit_count}"


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_7(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Single root multi-names.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 2)
    psb = PathStructureBuilder(rs)
    r0, r1 = rs
    Path(r1).rmdir()
    Path(r1).symlink_to(Path(r0), target_is_directory=True)
    psb.create_files_in_dirs(r0, {"": 8, "a": 8, "b": 8, "c": 8})
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(p.startswith(r0) for p in ps.keys())


@pytest.mark.asyncio
async def test_indexer_scheduler_poll_sync_8(tmpdir, indexer_scheduler_tester: SchedulerTesterBase):
    """
    Multi-root cross-symlinks.
    """
    rs = indexer_scheduler_tester.generate_tmp_roots_change_config(tmpdir, 2)
    psb = PathStructureBuilder(rs)
    r0, r1 = rs

    # Initial indexing of content
    fs0 = psb.create_files_in_dirs(r0, {"": 4, "a": 4, "b": 4, "c": 4})
    fs1 = psb.create_files_in_dirs(r1, {"": 4, "j": 4, "k": 4, "l": 4})
    await indexer_scheduler_tester.run_check_repeated_polls(psb)

    # test cross symlinks being ignored
    Path(r0, "rs").symlink_to(Path(r1), target_is_directory=True)
    Path(r1, "rs").symlink_to(Path(r0), target_is_directory=True)
    Path(r0, "js").symlink_to(Path(r1, "j"), target_is_directory=True)
    Path(r1, "as").symlink_to(Path(r0, "a"), target_is_directory=True)
    Path(r0, "a/ks").symlink_to(Path(r1, "k"), target_is_directory=True)
    Path(r1, "j/bs").symlink_to(Path(r0, "b"), target_is_directory=True)
    Path(r0, "kf0s").symlink_to(Path(r1, "k", fs1["k"][0]))
    Path(r1, "bf0s").symlink_to(Path(r0, "b", fs0["b"][0]))
    Path(r0, "b/lf0s").symlink_to(Path(r1, "l", fs1["l"][0]))
    Path(r1, "k/cf0s").symlink_to(Path(r0, "c", fs0["c"][0]))
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(p.startswith(r0) or p.startswith(r1) for p in ps.keys())
    assert Path(r0, "rs").as_posix() not in ps
    assert Path(r1, "rs").as_posix() not in ps
    accs_check = {"a": (["//a"], []), "b": (["//b"], []), "c": (["//c"], [])}
    _assert_accessors(ps, r0, accs_check)
    accs_check = {"j": (["//j"], []), "k": (["//k"], []), "l": (["//l"], [])}
    _assert_accessors(ps, r1, accs_check)

    # test some changes
    psb.update_files_in_dirs(r0, {"": 2, "a": 2, "b": 2, "c": 2})
    psb.update_files_in_dirs(r1, {"": 2, "j": 2, "k": 2, "l": 2})
    ps = await indexer_scheduler_tester.run_check_repeated_polls(psb)
    assert all(p.startswith(r0) or p.startswith(r1) for p in ps.keys())

    # check usage of soft commits
    sc = [i for i in indexer_scheduler_tester.soft_commits if i > 1]
    assert 2.5 < (x := sum(sc) / len(sc)) < 15.5, f"{x}\n{sc}"
