Skip to content

chore: Release v1.0.0.dev16 - #19

Merged
vistart merged 23 commits into
mainfrom
release/v1.0.0.dev16
Aug 14, 2026
Merged

vistart merged 23 commits into
mainfrom
release/v1.0.0.dev16

Conversation

@vistart

@vistart vistart commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Description

Release python-activerecord-testsuite v1.0.0.dev16 to main.

Key highlights of this release:

  • Sync/async test parity: completed 92-topic sync/async parity, fixed all async fixture mismatches across composite_pk, worker, events, derived_field, mixins; removed redundant @pytest.mark.asyncio decorators and resolved P6 line-count parity violations.
  • Composite primary key: added composite-PK testsuite contracts, column-mapping tests, and fulltext_search capability documentation.
  • Feature reorganization: merged composite_pk/derived_field features into basic/query; declared benchmark/fastapi as async_only.
  • Transaction contracts: added savepoint, pool dispatch, and async owner-task contracts.
  • Capability-aware assertions: --scenarios pytest option (early-parsed into TESTSUITE_ACTIVE_SCENARIOS), precision-tolerant datetime assertions, explain-plan skips.
  • Collection fixes: resolved duplicate basenames (test_derived_field* → test_relation_derived_field*) and undefined fixtures fixture; pytest_collection_modifyitems hook marked trylast.
  • Worker atomic updates: concurrent transfer tasks now use atomic conditional UPDATEs on backends without FOR UPDATE (e.g. Oracle), eliminating lost updates.
  • Python 3.8 compat: avoided str.removeprefix.

Release/Hotfix Details

  • Target Version: v1.0.0.dev16
  • Type: Pre-Release (dev)
  • Key Changes:
    • New Features
    • Bug Fixes
    • Breaking Changes
    • Security Fixes

Related Issues/PRs

None (changelog fragments not recorded for the test suite).

Breaking Change

  • Yes
  • No

Testing

As a shared test suite library, verification occurs through integration in
dependent rhosocial-activerecord backend projects and the core project.

  • All CI checks passed on the source branch of this repository.
  • Verification has been confirmed through CI runs in dependent projects
    (e.g., python-activerecord-oracle with test_concurrent_transactions_isolation passing on 18c/21c/23c; core/mysql/postgres/mariadb/sqlserver/firebird CI green).

Test Plan:
Confirmed CI runs across the backend ecosystem using this test suite version
(release/v1.0.0.dev16): core dev29, mysql dev19, postgres dev16, mariadb/oracle/sqlserver dev2, firebird dev1.

Checklist

  • My changes follow the project's version control guidelines (especially for release and hotfix procedures).
  • I have updated the CHANGELOG.md using towncrier build (for releases). (No fragments — test suite does not record changelog fragments.)
  • All necessary documentation (e.g., release notes, migration guides) has been prepared or updated.
  • I have confirmed all CI checks have passed on the target branch after merging (post-merge verification).

Additional Notes

This release is part of a coordinated series release (core dev29 ↔ test suite dev16 ↔ backends).

vistart and others added 20 commits June 16, 2026 10:31
Add DRY testsuite for composite primary key support:
- ICompositePKProvider interface
- Fixture models (OrderItem, StoreInventory, Order) with sync/async variants
- Parameterized conftest with scenario-based fixtures
- Test files: ActiveRecord CRUD, ActiveQuery, CTE, set operations
- Both sync and async test coverage
- Fix skip_test_if_protocol_unsupported to resolve dotted string paths
  via importlib.import_module instead of silently failing
- Update test_fulltext_search_compatibility marker to use
  IndexSupport protocol with supports_fulltext_search
…earch capability doc

- composite_pk/fixtures/models: add MappedOrderItem/AsyncMappedOrderItem
  using Annotated[int, UseColumn('order_id'/'product_id')] to exercise
  Python field name != DB column name with composite PK.
- composite_pk/interfaces: declare setup_mapped_order_item_model and
  async counterpart on ICompositePKProvider.
- composite_pk/conftest: expose mapped_order_item_class and
  async_mapped_order_item_class fixtures.
- composite_pk/test_activerecord: add TestCompositePKWithColumnMapping
  covering meta introspection, insert/find_one/find_all by field name
  and column name, tuple lookup, duplicate-PK integrity error, and
  refresh; switch _build_pk_where_predicate missing-column expectation
  from KeyError to ValueError to match current implementation.
- query/test_cross_database_compatibility: annotate fulltext search
  test with rationale for using supports_fulltext_search (query capability)
  rather than supports_fulltext_index (DDL).
- Rename test_sync_queries.py -> test_queries.py and test_async_queries.py -> test_queries_async.py
- Delete stale _sync.py benchmark files and related _sync.py bridge files
- Add 70+ new async bridge files for full feature parity
- Fix test_example_basic_fixtures_async.py: ActiveRecord -> AsyncActiveRecord
- Fix test_bulk_operations_async.py: add missing ColumnNameKey/UpdateValues
- Fix test_pydantic_native_validation_async.py: add 13 missing Async* model classes
- Fix test_threadsafe_dict_async.py: remove await from sync method
…r, events, derived_field, mixins

Root cause: split_tests.py copied sync test files without updating
fixture references, causing async tests to use sync fixtures that
return sync ActiveRecord models instead of AsyncActiveRecord.

Fixes:
- composite_pk: order_item_class -> async_order_item_class, etc.
- worker: AsyncUser.configure() is async def, need asyncio.run() in
  subprocess task functions (sync context)
- events: add async_event_model fixture + provider support
- derived_field: add AsyncProductFormA model + async_product_form_a_class
  fixture; fix fixture refs for proxy/adapter classes
- mixins: add 4 async model classes + 4 async fixtures + provider support
- Add await on query chain terminators (.one(), .all()) for async model
  query_with_deleted() / query_only_deleted() calls
Mirror the core sync/async parity fix (same-name async overrides,
AsyncSoftDeleteMixin) across the testsuite:

- await _trigger_event/validate_fields/restore in async tests
  (events, mixins, basic, query, relation, composite_pk, etc.)
- switch AsyncTask/AsyncCombinedArticle to AsyncSoftDeleteMixin
- add async setup_*/cleanup_after_test_async to events, mixins and
  derived_field providers
- gitignore local src.bak*/ and tools/ scratch directories

Co-Authored-By: Claude <noreply@anthropic.com>
Four related changes restoring sync/async parity:

1. Remove feature/query/fixtures/async_mapped_models.py, the only
   module importing sqlalchemy (Mapped* via mapped_column). It had
   drifted from the sync MappedUser schema and referenced a dangling
   AsyncMappedOrder, causing ModuleNotFoundError on CI (Python 3.9
   where sqlalchemy is not installed).

2. Add native AsyncMappedUser/Post/Comment to fixtures/models.py,
   mirroring MappedUser with rhosocial UseColumn annotations and
   AsyncHasMany/AsyncBelongsTo relations. Field/column mappings are
   identical to the sync counterpart.

3. Fix test_special_queries_async.py: fixture name typo
   async_extended_async_order_fixtures -> async_extended_order_fixtures
   (duplicated async_ prefix), drop unused imports/vars, wrap long line.

4. Move ruff select/ignore under [tool.ruff.lint] to silence the
   deprecated top-level option warning.

Co-Authored-By: Claude <noreply@anthropic.com>
…line-count parity violations

Global asyncio_mode=auto (pyproject.toml) makes markers redundant:
- Remove 681 @pytest.mark.asyncio decorators across 68 async test files
- Remove unused import pytest from 1 file
- Update check_markers.py to reject (not require) the marker

Fix sync/async parity (P6) violations:
- test_parallel_queries: add missing async_query_order_items_task to sync
- test_eager_loading_scenarios_async: trim 32 trailing blank lines
- test_logging_data_summarization_async: remove dead sync helper functions
- test_pydantic_native_validation_async: add missing sync model classes
- 4 files with excess blank lines normalized

Fix bug: _count_async_test_methods now counts top-level async functions
Adjust P6 thresholds: P6_file_lines at actual_extra < -40,
P6_file_lines_extra at actual_extra > 20

Update rule docs: sync_async_parity.md, test_case_naming_and_markers.md,
development_tools.md
…sync duplicates

- Add __parity__ = "async_only" to benchmark/fastapi/conftest.py so the
  sync/async parity checker skips P1 orphan checks for this directory.
- Remove 5 sync test files that duplicated async-only benchmarks
  (test_health.py, test_read.py, test_write.py,
  test_concurrent_read.py, test_concurrent_transaction.py).
- Add docstrings to the corresponding _async.py test functions.
- Document the __parity__ convention in .claude/rules/sync_async_parity.md
  (Rule 1 exception, new Per-directory parity mode section, Known
  Exceptions entry, updated workflow).
…asic/query

- Delete feature/composite_pk/ and feature/derived_field/ directories
- Move derived_field tests to basic/fields/
- Move composite_pk crud tests to basic/crud/, query tests under query/{basic,cte,set_operations}
- Migrate fixture models/models_py310-312 with composite_pk/derived_field classes
- Reorganize query/relation tests into subdirectories (aggregate, basic, eager_loading, etc.)
- Add new conftest.py fixtures for composite_pk, derived_field scenarios
…le proper teardown

The async fixtures in testsuite/feature/query/conftest.py were calling
backend.disconnect() BEFORE provider.cleanup_after_test(). This caused
the subsequent provider's DROP TABLE statements to silently fail because
the connection was already closed, leaving test tables in the database
between tests.

Removal lets cleanup_after_test() handle both DROP TABLE and disconnect
in the correct order, ensuring reliable test isolation across scenarios
and within sequential test runs.

Affects 10 fixtures: async_order_fixtures, async_blog_fixtures,
async_json_user_fixture, async_tree_fixtures, async_combined_fixtures,
async_extended_order_fixtures, async_annotated_query_fixtures,
async_mapped_models_fixtures, async_profile_fixtures, and leaves
async_order_item_class (already correct) unchanged.
… contracts

Add the first batch of transaction contract suites covering three
subsystems:

- Savepoint API black-box contracts (test_savepoint_api.py /
  test_savepoint_api_async.py): 32 tests for TransactionManager
  savepoint(), release(), and rollback_to() including auto-naming,
  explicit names, unknown-name and out-of-transaction error paths,
  non-stack truncation semantics, data-level rollback proof, and
  outermost commit with unreleased savepoints.

- Pool transaction dispatch contracts (test_pool_transaction_paths.py /
  _async.py): 30 tests for BackendPool.transaction and
  AsyncBackendPool.transaction covering three exclusive dispatch
  branches: existing-transaction reuse (Path 1), connection-only
  begin/commit and token cleanup (Path 2), and fresh-acquire cycle
  with commit/release (Path 3), plus rollback-on-exception paths and
  precedence ordering.

- AsyncTransactionManager cross-task owner-task warning (test_async_
  owner_task.py): 7 tests verifying the _owner_task safeguard at
  AsyncTransactionManager.transaction() entry.  The I/O layer is
  stubbed because aiosqlite's worker thread prevents a second asyncio
  task from safely reusing the same backend instance; the stub
  isolates the pure state-machine contract.

All sync/async mirrors created. 69 new tests pass against the SQLite
provider; 30 existing connection tests pass without regression.
Several testsuite tests asserted strict equality of created_at,
updated_at, and deleted_at across database round-trips. These broke
on backends that store timestamps with less than 6-digit Python
precision:

- Firebird: TIMESTAMP defaults to 4-digit sub-second precision
  (.124674 -> .124600).
- Oracle: DATE carries no fractional seconds.
- SQL Server: datetime rounds to 1/300-second ticks (~3.3 ms).

Replace strict equality with a new helper pair in
rhosocial.activerecord.testsuite.utils:

- assert_datetime_equal(actual, expected, *, tolerance=10ms)
- assert_datetime_close(actual, reference, *, tolerance=10ms)

Default tolerance (10 ms) is generous enough to cover Firebird,
Oracle DATE, and SQL Server rounding, while still surfacing real
off-by-large-interval bugs. Tests comparing two in-memory model
fields (no DB round-trip) explicitly pass tolerance=timedelta(0)
to preserve the original strict semantics.

Affected tests:
- basic/crud/test_crud.py::test_update_user (and _async)
- mixins/test_soft_delete.py::test_soft_delete_basic (and _async)
- mixins/test_combined_articles.py::test_combined_update (and _async)
- mixins/test_timestamps.py (3 cases)
- mixins/test_timestamps_async.py (3 cases)

Add tests/rhosocial/activerecord/testsuite/utils/test_datetime_helpers.py
with 10 unit tests covering normal/tolerance/strict/tz/None/error-
message behaviour. All pass; ruff clean; mypy unchanged (only
pre-existing missing-annotation errors remain).
…_protocol contracts

Move the ALTER TABLE IF [NOT] EXISTS coverage from a standalone top-level
'feature/ddl' topic into the 'basic' topic as a second-level ddl subtopic.
The tests are a generic expression/dialect contract: unsupported backends
skip via @requires_protocol(AlterTableModifierSupport, ...) markers instead
of in-test capability branches.
str.removeprefix was added in Python 3.9; the testsuite targets 3.8+.
Use replace('supports_', '') to strip the capability prefix in the
requires_protocol skip message.
…xture

Rename relation/derived test_derived_field* files to unique basenames so the
full feature tree collects under default pytest import mode, and drop the
undefined 'fixtures' fixture parameter from feature/test_features*.py that
caused 6 collection errors on every backend. Mark the root conftest's
pytest_collection_modifyitems hook as trylast so backend-owned collection
hooks take precedence. Also rename the async savepoint fixture to
async_txn_backend per the async naming convention.
…rtions

- Register generic --scenarios pytest option, early-parsed into
  TESTSUITE_ACTIVE_SCENARIOS before provider modules import
- Make datetime field assertions precision-tolerant via
  supports_microsecond_timestamp() dialect capability
- Skip explain assertions when dialect lacks supports_explain_plan()
- Use case-insensitive SQL fragment assertions (order_number, CTE names)
- Accept "validation error" as NOT NULL violation message
- Add changelog fragment
@vistart vistart self-assigned this Aug 14, 2026
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
11.6% Duplication on New Code (required ≤ 3%)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@vistart
vistart merged commit 6fdd8b2 into main Aug 14, 2026
0 of 2 checks passed
vistart added a commit that referenced this pull request Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant