From 9520d619cc15fc17bc8d3b265e341d06e9fa162d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:11:08 +0000 Subject: [PATCH 1/3] Initial plan From 0da0d2a3b33907ccfbff5b1f5dfdeddcf7a1119f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:15:07 +0000 Subject: [PATCH 2/3] Add comprehensive tests for ds.py module Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com> --- tests/test_ds.py | 190 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 tests/test_ds.py diff --git a/tests/test_ds.py b/tests/test_ds.py new file mode 100644 index 0000000..f8566b4 --- /dev/null +++ b/tests/test_ds.py @@ -0,0 +1,190 @@ +import asyncio +import tempfile +import pathlib +import pytest +import pytest_asyncio +from sqlalchemy import select +from ddss.orm import initialize_database, Facts, Ideas +from ddss.ds import main + + +@pytest_asyncio.fixture +async def temp_db(): + """Fixture to create a temporary database.""" + with tempfile.TemporaryDirectory() as tmpdir: + db_path = pathlib.Path(tmpdir) / "test.db" + addr = f"sqlite+aiosqlite:///{db_path.as_posix()}" + engine, session = await initialize_database(addr) + yield addr, engine, session + await engine.dispose() + + +@pytest.mark.asyncio +async def test_ds_simple_modus_ponens(temp_db): + """Test simple modus ponens: 'a -> b' with '|- a' produces '|- b'.""" + addr, engine, session = temp_db + + # Add initial facts: a -> b and |- a + async with session() as sess: + sess.add(Facts(data="a\n----\nb\n")) + sess.add(Facts(data="----\na\n")) + await sess.commit() + + # Run the main function with a timeout + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.3) # Give it time to process + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Check that the new fact '|- b' was created + async with session() as sess: + all_facts = await sess.scalars(select(Facts)) + facts_data = [f.data for f in all_facts] + + assert "----\nb\n" in facts_data + + +@pytest.mark.asyncio +async def test_ds_multi_premise_with_idea(temp_db): + """Test multi-premise rule: 'a, b -> c' with '|- a' produces 'b -> c' and idea '|- b'.""" + addr, engine, session = temp_db + + # Add initial facts: a, b -> c and |- a + async with session() as sess: + sess.add(Facts(data="a\nb\n----\nc\n")) + sess.add(Facts(data="----\na\n")) + await sess.commit() + + # Run the main function with a timeout + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.3) # Give it time to process + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Check that the new fact 'b -> c' was created + async with session() as sess: + all_facts = await sess.scalars(select(Facts)) + facts_data = [f.data for f in all_facts] + + assert "b\n----\nc\n" in facts_data + + # Check that the idea '|- b' was created + async with session() as sess: + all_ideas = await sess.scalars(select(Ideas)) + ideas_data = [i.data for i in all_ideas] + + assert "----\nb\n" in ideas_data + + +@pytest.mark.asyncio +async def test_ds_no_inference_without_matching_facts(temp_db): + """Test that no inference occurs when facts don't match.""" + addr, engine, session = temp_db + + # Add facts that don't match: a -> b and |- c + async with session() as sess: + sess.add(Facts(data="a\n----\nb\n")) + sess.add(Facts(data="----\nc\n")) + await sess.commit() + + # Run the main function with a timeout + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.3) # Give it time to process + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Check that only the original facts exist (no new inference) + async with session() as sess: + all_facts = await sess.scalars(select(Facts)) + facts_data = [f.data for f in all_facts] + + # Should only have the original 2 facts + assert len(facts_data) == 2 + assert "a\n----\nb\n" in facts_data + assert "----\nc\n" in facts_data + + +@pytest.mark.asyncio +async def test_ds_multiple_inferences(temp_db): + """Test multiple inference steps in sequence.""" + addr, engine, session = temp_db + + # Add facts for chained inference: a -> b, b -> c, |- a + async with session() as sess: + sess.add(Facts(data="a\n----\nb\n")) + sess.add(Facts(data="b\n----\nc\n")) + sess.add(Facts(data="----\na\n")) + await sess.commit() + + # Run the main function with a timeout + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.5) # Give it time for multiple rounds + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Check that both |- b and |- c were inferred + async with session() as sess: + all_facts = await sess.scalars(select(Facts)) + facts_data = [f.data for f in all_facts] + + assert "----\nb\n" in facts_data + assert "----\nc\n" in facts_data + + +@pytest.mark.asyncio +async def test_ds_cancellation(temp_db): + """Test that the ds main function can be cancelled without hanging.""" + addr, engine, session = temp_db + + # Run the main function and cancel it + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.1) # Let it start + task.cancel() + + # Should complete without hanging + try: + await task + except asyncio.CancelledError: + pass # Expected - cancellation worked + + +@pytest.mark.asyncio +async def test_ds_duplicate_facts_not_added(temp_db): + """Test that duplicate facts are not added to the database.""" + addr, engine, session = temp_db + + # Add facts that will produce a duplicate: a -> b twice with |- a + async with session() as sess: + sess.add(Facts(data="a\n----\nb\n")) + sess.add(Facts(data="----\na\n")) + await sess.commit() + + # Run the main function + task = asyncio.create_task(main(addr, engine, session)) + await asyncio.sleep(0.3) + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Count the facts - should have 3: original 2 + 1 inferred |- b + async with session() as sess: + all_facts = await sess.scalars(select(Facts)) + facts_list = list(all_facts) + + assert len(facts_list) == 3 + facts_data = [f.data for f in facts_list] + assert facts_data.count("----\nb\n") == 1 # Should only appear once From ae1a5cc519b973bbd53dfc46339cac475bc801b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:24:55 +0000 Subject: [PATCH 3/3] Update comment style from -> and |- to => notation Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com> --- tests/test_ds.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_ds.py b/tests/test_ds.py index f8566b4..34fefe4 100644 --- a/tests/test_ds.py +++ b/tests/test_ds.py @@ -21,10 +21,10 @@ async def temp_db(): @pytest.mark.asyncio async def test_ds_simple_modus_ponens(temp_db): - """Test simple modus ponens: 'a -> b' with '|- a' produces '|- b'.""" + """Test simple modus ponens: 'a => b' with '=> a' produces '=> b'.""" addr, engine, session = temp_db - # Add initial facts: a -> b and |- a + # Add initial facts: a => b and => a async with session() as sess: sess.add(Facts(data="a\n----\nb\n")) sess.add(Facts(data="----\na\n")) @@ -39,7 +39,7 @@ async def test_ds_simple_modus_ponens(temp_db): except asyncio.CancelledError: pass - # Check that the new fact '|- b' was created + # Check that the new fact '=> b' was created async with session() as sess: all_facts = await sess.scalars(select(Facts)) facts_data = [f.data for f in all_facts] @@ -49,10 +49,10 @@ async def test_ds_simple_modus_ponens(temp_db): @pytest.mark.asyncio async def test_ds_multi_premise_with_idea(temp_db): - """Test multi-premise rule: 'a, b -> c' with '|- a' produces 'b -> c' and idea '|- b'.""" + """Test multi-premise rule: 'a, b => c' with '=> a' produces 'b => c' and idea '=> b'.""" addr, engine, session = temp_db - # Add initial facts: a, b -> c and |- a + # Add initial facts: a, b => c and => a async with session() as sess: sess.add(Facts(data="a\nb\n----\nc\n")) sess.add(Facts(data="----\na\n")) @@ -67,14 +67,14 @@ async def test_ds_multi_premise_with_idea(temp_db): except asyncio.CancelledError: pass - # Check that the new fact 'b -> c' was created + # Check that the new fact 'b => c' was created async with session() as sess: all_facts = await sess.scalars(select(Facts)) facts_data = [f.data for f in all_facts] assert "b\n----\nc\n" in facts_data - # Check that the idea '|- b' was created + # Check that the idea '=> b' was created async with session() as sess: all_ideas = await sess.scalars(select(Ideas)) ideas_data = [i.data for i in all_ideas] @@ -87,7 +87,7 @@ async def test_ds_no_inference_without_matching_facts(temp_db): """Test that no inference occurs when facts don't match.""" addr, engine, session = temp_db - # Add facts that don't match: a -> b and |- c + # Add facts that don't match: a => b and => c async with session() as sess: sess.add(Facts(data="a\n----\nb\n")) sess.add(Facts(data="----\nc\n")) @@ -118,7 +118,7 @@ async def test_ds_multiple_inferences(temp_db): """Test multiple inference steps in sequence.""" addr, engine, session = temp_db - # Add facts for chained inference: a -> b, b -> c, |- a + # Add facts for chained inference: a => b, b => c, => a async with session() as sess: sess.add(Facts(data="a\n----\nb\n")) sess.add(Facts(data="b\n----\nc\n")) @@ -134,7 +134,7 @@ async def test_ds_multiple_inferences(temp_db): except asyncio.CancelledError: pass - # Check that both |- b and |- c were inferred + # Check that both => b and => c were inferred async with session() as sess: all_facts = await sess.scalars(select(Facts)) facts_data = [f.data for f in all_facts] @@ -165,7 +165,7 @@ async def test_ds_duplicate_facts_not_added(temp_db): """Test that duplicate facts are not added to the database.""" addr, engine, session = temp_db - # Add facts that will produce a duplicate: a -> b twice with |- a + # Add facts that will produce a duplicate: a => b twice with => a async with session() as sess: sess.add(Facts(data="a\n----\nb\n")) sess.add(Facts(data="----\na\n")) @@ -180,7 +180,7 @@ async def test_ds_duplicate_facts_not_added(temp_db): except asyncio.CancelledError: pass - # Count the facts - should have 3: original 2 + 1 inferred |- b + # Count the facts - should have 3: original 2 + 1 inferred => b async with session() as sess: all_facts = await sess.scalars(select(Facts)) facts_list = list(all_facts)