Skip to content

Commit 5a412b4

Browse files
committed
fix: Resolved error with CI steps for coverage and test
1 parent 1cca42f commit 5a412b4

8 files changed

Lines changed: 115 additions & 162 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515
branches: [main]
1616

1717
jobs:
18-
test:
18+
build-and-test:
1919
runs-on: ubuntu-latest
2020

2121
steps:
@@ -32,48 +32,22 @@ jobs:
3232
with:
3333
python-version-file: ".python-version"
3434

35-
3635
- name: Build the project
3736
run: |
3837
docker compose build sqlnotify
3938
4039
- name: Run tests
4140
run: make test
4241

43-
- name: Run tests with coverage
44-
run: make test_coverage
45-
4642
- name: Upload coverage to Codecov
47-
uses: codecov/codecov-action@v4
48-
with:
49-
file: ./coverage.xml
50-
fail_ci_if_error: true
51-
token: ${{ secrets.CODECOV_TOKEN }}
52-
53-
- name: Compose down
54-
run: docker compose down --remove-orphans
55-
56-
build:
57-
runs-on: ubuntu-latest
58-
59-
steps:
60-
- uses: actions/checkout@v4
61-
62-
- name: Install uv
63-
uses: astral-sh/setup-uv@v5
64-
with:
65-
enable-cache: true
66-
cache-dependency-glob: uv.lock
67-
68-
- name: Set up Python
69-
uses: actions/setup-python@v5
70-
with:
71-
python-version-file: "3.14"
72-
73-
- name: Build the project
74-
run: make build
43+
run: make coverage
44+
env:
45+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
7546

7647
- name: Publish to PyPI
7748
run: make publish
7849
env:
79-
token: ${{ secrets.PYPI_TOKEN }}
50+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
51+
52+
- name: Compose down
53+
run: docker compose down --remove-orphans

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \
88
gcc \
99
build-essential \
1010
gettext \
11+
gnupg \
1112
musl-dev \
1213
sqlite3 \
1314
&& rm -rf /var/lib/apt/lists/*
@@ -23,6 +24,7 @@ ENV UV_COMPILE_BYTECODE=1
2324
RUN uv venv .venv
2425
ENV VIRTUAL_ENV=/app/.venv
2526
ENV PATH="/app/.venv/bin:$PATH"
27+
RUN pip install --no-cache-dir codecov-cli
2628
ENV PYTHONPATH=/app/
2729
ENV PYTHONUNBUFFERED=1
2830

Makefile

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
UV := uv
2+
CODECOV_CLI := codecovcli
23

3-
.PHONY: test test_unit test_sqlite test_postgres test_coverage test_all build publish release
4+
.PHONY: test build publish release coverage
45

56

67
build:
@@ -11,41 +12,23 @@ build:
1112

1213
publish:
1314
@echo "Publishing to PyPI..."
14-
@$(UV) publish -t $(token)
15+
@$(UV) publish -t $(PYPI_TOKEN)
1516
@echo "Published successfully"
1617

1718

19+
coverage:
20+
@echo "Running coverage..."
21+
@$(UV) run pytest --cov=sqlnotify --cov-report=xml
22+
@$(CODECOV_CLI) upload-process -t $(CODECOV_TOKEN) -f coverage.xml
23+
@echo "Coverage report generated at coverage.xml"
24+
25+
1826
release: build
1927
@echo "Running release script..."
2028
@bash scripts/release.sh $(ARGS)
2129

2230

23-
test_all: test test_coverage
24-
@echo "All tests and coverage completed"
25-
26-
test_coverage:
27-
@echo "Running tests coverage for sqlnotify..."
28-
@docker compose run --remove-orphans sqlnotify bash -c "$(UV) run pytest --cov=sqlnotify --cov-report=xml"
29-
@echo "Test coverage report generated at coverage.xml"
30-
31-
32-
test: test_unit test_postgres test_sqlite
31+
test:
32+
@echo "Running all tests for sqlnotify..."
33+
@docker compose run --remove-orphans sqlnotify bash -c "$(UV) run pytest"
3334
@echo "All tests completed"
34-
35-
36-
test_unit:
37-
@echo "Running all unit tests for sqlnotify..."
38-
@docker compose run --remove-orphans sqlnotify bash -c "$(UV) run pytest tests/test_watcher.py"
39-
@echo "All Unit tests completed"
40-
41-
42-
test_sqlite:
43-
@echo "Running tests for sqlnotify sqlite..."
44-
@docker compose run --remove-orphans sqlnotify bash -c "$(UV) run pytest tests/test_sqlite_*.py --db=sqlite"
45-
@echo "All SQLite tests completed"
46-
47-
48-
test_postgres:
49-
@echo "Running tests for sqlnotify postgres..."
50-
@docker compose run --remove-orphans sqlnotify bash -c "$(UV) run pytest tests/test_postgres_*.py --db=postgresql"
51-
@echo "All PostgreSQL tests completed"

tests/conftest.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,63 @@
1111

1212
patch.dict(
1313
os.environ,
14-
{
15-
"POSTGRES_HOST": "postgres"
16-
},
14+
{"POSTGRES_HOST": "postgres"},
1715
).start() # noqa
1816

1917

20-
def pytest_addoption(parser):
21-
parser.addoption(
22-
"--db",
23-
action="store",
24-
default="postgresql",
25-
help="Database type to use for tests: postgresql or sqlite",
26-
)
18+
def pytest_collection_modifyitems(config, items):
19+
required = set()
2720

21+
for item in items:
22+
fname = os.path.basename(str(item.fspath)).lower()
23+
if fname.startswith("test_sqlite"):
24+
required.add("sqlite")
25+
if fname.startswith("test_postgres") or fname.startswith("test_postgresql"):
26+
required.add("postgresql")
2827

29-
@pytest.fixture(scope="session")
28+
if not required:
29+
required.add(os.getenv("TEST_DB", "postgresql"))
30+
31+
config._required_db_types = required
32+
33+
34+
@pytest.fixture
3035
def db_type(request) -> str:
31-
return request.config.getoption("--db")
36+
fname = os.path.basename(str(request.node.fspath)).lower()
37+
if fname.startswith("test_sqlite"):
38+
return "sqlite"
39+
if fname.startswith("test_postgres") or fname.startswith("test_postgresql"):
40+
return "postgresql"
41+
return os.getenv("TEST_DB", "postgresql")
3242

3343

3444
@pytest.fixture(scope="session")
35-
def database_manager(db_type: str) -> DatabaseManager:
36-
return DatabaseManager(db_type=db_type)
45+
def database_manager() -> DatabaseManager:
46+
return DatabaseManager()
3747

3848

3949
@pytest.fixture(scope="session", autouse=True)
40-
def manage_test_database(database_manager: DatabaseManager):
41-
database_manager.create_test_database()
50+
def manage_test_database(request, database_manager: DatabaseManager):
51+
required = getattr(request.config, "_required_db_types", None)
52+
if not required:
53+
required = {os.getenv("TEST_DB", "postgresql")}
54+
for db in required:
55+
database_manager.create_test_database(db_type=db)
4256

4357
yield
4458

45-
database_manager.drop_test_database()
59+
for db in required:
60+
database_manager.drop_test_database(db_type=db)
4661

4762

48-
@pytest.fixture(scope="function")
63+
@pytest.fixture
4964
async def async_engine(
5065
database_manager: DatabaseManager,
66+
db_type: str,
5167
) -> AsyncGenerator[AsyncEngine, None]:
52-
engine = database_manager.create_async_engine()
68+
engine = database_manager.create_async_engine(db_type=db_type)
5369

54-
await database_manager.create_tables_async(engine)
70+
await database_manager.create_tables_async(engine, db_type=db_type)
5571

5672
yield engine
5773

@@ -83,11 +99,13 @@ async def async_session_factory(async_engine: AsyncEngine):
8399
)
84100

85101

86-
@pytest.fixture(scope="session")
87-
def sync_engine(database_manager: DatabaseManager) -> Generator[Engine, None, None]:
88-
engine = database_manager.create_sync_engine()
102+
@pytest.fixture(scope="function")
103+
def sync_engine(
104+
database_manager: DatabaseManager, db_type: str
105+
) -> Generator[Engine, None, None]:
106+
engine = database_manager.create_sync_engine(db_type=db_type)
89107

90-
database_manager.create_tables_sync(engine)
108+
database_manager.create_tables_sync(engine, db_type=db_type)
91109

92110
yield engine
93111

tests/database.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515

1616
class DatabaseManager:
1717

18-
def __init__(self, db_type: str = "postgresql"):
18+
def __init__(self, db_type: str | None = None):
1919
self.db_type = db_type
2020
self._engine: Engine | AsyncEngine | None = None
2121
self._async_session_factory = None
2222
self._sync_session_factory = None
2323

24-
def get_database_url(self, async_driver: bool = False) -> str:
24+
def get_database_url(
25+
self, async_driver: bool = False, db_type: str | None = None
26+
) -> str:
27+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
2528

26-
if self.db_type == "sqlite":
29+
if db_type == "sqlite":
2730
if async_driver:
2831
return (
2932
f"sqlite+aiosqlite:///{os.getenv('SQLITE_DB', 'test_sqlnotify.db')}"
@@ -44,8 +47,9 @@ def get_database_url(self, async_driver: bool = False) -> str:
4447

4548
return f"postgresql+psycopg://{user}:{password}@{host}:{port}/{db_name}"
4649

47-
def get_base_connection_url(self) -> str:
48-
if self.db_type == "sqlite":
50+
def get_base_connection_url(self, db_type: str | None = None) -> str:
51+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
52+
if db_type == "sqlite":
4953
return "" # SQLite doesn't need base connection
5054

5155
user = os.getenv("POSTGRES_USER", "postgres")
@@ -55,15 +59,16 @@ def get_base_connection_url(self) -> str:
5559

5660
return f"postgresql+psycopg://{user}:{password}@{host}:{port}/postgres"
5761

58-
def create_test_database(self) -> None:
62+
def create_test_database(self, db_type: str | None = None) -> None:
63+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
5964

60-
if self.db_type == "sqlite":
65+
if db_type == "sqlite":
6166
return
6267

6368
worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master")
6469
db_name = f"{os.getenv('POSTGRES_DB', 'sqlnotify_test')}_{worker_id}"
6570

66-
base_url = self.get_base_connection_url()
71+
base_url = self.get_base_connection_url(db_type=db_type)
6772
admin_engine = create_engine(base_url, isolation_level="AUTOCOMMIT")
6873

6974
with admin_engine.connect() as conn:
@@ -72,9 +77,10 @@ def create_test_database(self) -> None:
7277

7378
admin_engine.dispose()
7479

75-
def drop_test_database(self) -> None:
80+
def drop_test_database(self, db_type: str | None = None) -> None:
81+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
7682

77-
if self.db_type == "sqlite":
83+
if db_type == "sqlite":
7884
db_file = os.getenv("SQLITE_DB", "test_sqlnotify.db")
7985

8086
if os.path.exists(db_file):
@@ -85,28 +91,29 @@ def drop_test_database(self) -> None:
8591
worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master")
8692
db_name = f"{os.getenv('POSTGRES_DB', 'sqlnotify_test')}_{worker_id}"
8793

88-
base_url = self.get_base_connection_url()
94+
base_url = self.get_base_connection_url(db_type=db_type)
8995
admin_engine = create_engine(base_url, isolation_level="AUTOCOMMIT")
9096

9197
with admin_engine.connect() as conn:
9298
conn.execute(text(f"DROP DATABASE IF EXISTS {db_name} WITH (FORCE)"))
9399

94100
admin_engine.dispose()
95101

96-
def create_async_engine(self) -> AsyncEngine:
97-
98-
url = self.get_database_url(async_driver=True)
102+
def create_async_engine(self, db_type: str | None = None) -> AsyncEngine:
103+
url = self.get_database_url(async_driver=True, db_type=db_type)
99104
return create_async_engine(url, echo=False)
100105

101-
def create_sync_engine(self) -> Engine:
102-
103-
url = self.get_database_url(async_driver=False)
106+
def create_sync_engine(self, db_type: str | None = None) -> Engine:
107+
url = self.get_database_url(async_driver=False, db_type=db_type)
104108
return create_engine(url, echo=False)
105109

106-
async def create_tables_async(self, engine: AsyncEngine) -> None:
110+
async def create_tables_async(
111+
self, engine: AsyncEngine, db_type: str | None = None
112+
) -> None:
113+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
107114

108115
async with engine.begin() as conn:
109-
if self.db_type == "sqlite":
116+
if db_type == "sqlite":
110117

111118
def create_filtered_tables(connection):
112119
tables_to_create = [
@@ -121,8 +128,9 @@ def create_filtered_tables(connection):
121128
await conn.execute(text("CREATE SCHEMA IF NOT EXISTS analytics"))
122129
await conn.run_sync(SQLModel.metadata.create_all)
123130

124-
def create_tables_sync(self, engine: Engine) -> None:
125-
if self.db_type == "sqlite":
131+
def create_tables_sync(self, engine: Engine, db_type: str | None = None) -> None:
132+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
133+
if db_type == "sqlite":
126134
tables_to_create = [
127135
table
128136
for table in SQLModel.metadata.sorted_tables
@@ -135,10 +143,13 @@ def create_tables_sync(self, engine: Engine) -> None:
135143

136144
SQLModel.metadata.create_all(engine)
137145

138-
async def drop_tables_async(self, engine: AsyncEngine) -> None:
146+
async def drop_tables_async(
147+
self, engine: AsyncEngine, db_type: str | None = None
148+
) -> None:
149+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
139150

140151
async with engine.begin() as conn:
141-
if self.db_type == "sqlite":
152+
if db_type == "sqlite":
142153

143154
def drop_filtered_tables(connection):
144155
tables_to_drop = [
@@ -152,8 +163,9 @@ def drop_filtered_tables(connection):
152163
else:
153164
await conn.run_sync(SQLModel.metadata.drop_all)
154165

155-
def drop_tables_sync(self, engine: Engine) -> None:
156-
if self.db_type == "sqlite":
166+
def drop_tables_sync(self, engine: Engine, db_type: str | None = None) -> None:
167+
db_type = db_type or self.db_type or os.getenv("TEST_DB", "postgresql")
168+
if db_type == "sqlite":
157169
tables_to_drop = [
158170
table
159171
for table in SQLModel.metadata.sorted_tables

tests/test_postgres_dialect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_get_postgres_dialect_sync(self, sync_engine):
3333
def skip_if_not_postgres(self, db_type):
3434

3535
if db_type != "postgresql":
36-
pytest.skip("PostgreSQL functionality tests require --db=postgresql")
36+
pytest.skip("PostgreSQL functionality tests require a postgresql test run")
3737

3838
@pytest.fixture
3939
def watcher(self):

0 commit comments

Comments
 (0)