Skip to content

Commit aa23e4d

Browse files
authored
Merge branch 'develop' into dpe-1577-storage-locations
2 parents c113257 + fa8f6fa commit aa23e4d

127 files changed

Lines changed: 14568 additions & 31191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
# run unit (and integration tests if account secrets available) on our build matrix
4747
test:
4848
needs: [pre-commit]
49+
timeout-minutes: 180
4950

5051
strategy:
5152
fail-fast: false
@@ -83,7 +84,7 @@ jobs:
8384
path: |
8485
${{ steps.get-dependencies.outputs.site_packages_loc }}
8586
${{ steps.get-dependencies.outputs.site_bin_dir }}
86-
key: ${{ runner.os }}-${{ matrix.python }}-build-${{ env.cache-name }}-${{ hashFiles('setup.py') }}-v31
87+
key: ${{ runner.os }}-${{ matrix.python }}-build-${{ env.cache-name }}-${{ hashFiles('setup.py', 'setup.cfg') }}-v33
8788

8889
- name: Install py-dependencies
8990
if: steps.cache-dependencies.outputs.cache-hit != 'true'
@@ -208,6 +209,7 @@ jobs:
208209
209210
# Run only the failed tests
210211
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml \
212+
--html=integration-test-report.html --self-contained-html \
211213
--junit-xml=test-results.xml \
212214
-n 8 --dist loadscope \
213215
$(cat failed_tests.txt | tr '\n' ' ')
@@ -216,12 +218,13 @@ jobs:
216218
217219
# use loadscope to avoid issues running tests concurrently that share scoped fixtures
218220
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml \
221+
--html=integration-test-report.html --self-contained-html \
219222
--junit-xml=test-results.xml \
220223
tests/integration -n 8 $IGNORE_FLAGS --dist loadscope
221224
fi
222225
223226
# Execute the CLI tests in a non-dist way because they were causing some test instability when being run concurrently
224-
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml tests/integration/synapseclient/test_command_line_client.py
227+
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml --html=cli-test-report.html --self-contained-html tests/integration/synapseclient/test_command_line_client.py
225228
226229
- name: Extract Failed Tests
227230
if: always() && steps.integration_tests.outcome == 'failure'
@@ -291,6 +294,17 @@ jobs:
291294
echo "::error::Integration tests failed after ${{ github.run_attempt }} attempt(s)"
292295
exit 1
293296
297+
- name: Upload integration test HTML report
298+
# make sure report always gets uploaded if the integration tests ran, even if they failed, but skip if the integration tests were skipped
299+
if: always() && steps.integration_tests.outcome != 'skipped'
300+
uses: actions/upload-artifact@v4
301+
with:
302+
name: integration-test-report-${{ matrix.os }}-${{ matrix.python }}
303+
path: |
304+
integration-test-report.html
305+
cli-test-report.html
306+
retention-days: 14
307+
294308
- name: Upload coverage report
295309
id: upload_coverage_report
296310
uses: actions/upload-artifact@v4

CONTRIBUTING.md

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -180,25 +180,28 @@ Now that you have chosen a JIRA ticket and have your own fork of this repository
180180
181181
All code added to the client must have tests. These might include unit tests (to test specific functionality of code that was added to support fixing the bug or feature), integration tests (to test that the feature is usable - e.g., it should have complete the expected behavior as reported in the feature request or bug report), or both.
182182
183-
The Python client uses [`pytest`](https://docs.pytest.org/en/latest/) to run tests. The test code is located in the [test](./test) subdirectory.
183+
The Python client uses [`pytest`](https://docs.pytest.org/en/latest/) to run tests. The test code is located in the [tests](./tests) subdirectory.
184184
185185
Here's how to run the test suite:
186186
187-
> *Note:* The entire set of tests takes approximately 20 minutes to run.
188-
189187
```
190-
# Unit tests
191-
pytest -vs tests/unit
188+
# Unit tests (~1-2 minutes)
189+
pytest -sv tests/unit
190+
191+
# Integration tests (requires Synapse credentials, ~30-60 minutes)
192+
# Uses pytest-xdist for parallel execution with fixture-aware distribution
193+
pytest -sv tests/integration -n 8 --dist loadscope
192194

193-
# Integration tests - The integration tests should be run against the `dev` synapse server
194-
pytest -vs tests/integration
195+
# Integration tests excluding CLI tests (which must run serially)
196+
pytest -sv tests/integration -n 8 --dist loadscope \
197+
--ignore=tests/integration/synapseclient/test_command_line_client.py
195198
```
196199
197200
To test a specific feature, specify the full path to the function to run:
198201
199202
```
200203
# Test table query functionality from the command line client
201-
pytest -vs tests/integration/synapseclient/test_command_line_client.py::test_table_query
204+
pytest -sv tests/integration/synapseclient/test_command_line_client.py::test_table_query
202205
````
203206
204207
#### Integration testing against the `dev` synapse server
@@ -244,8 +247,8 @@ When adding support for a new Python version (e.g., adding Python 3.15), update
244247
245248
**CI/CD configuration files:**
246249
1. **`.github/workflows/build.yml`**:
247-
- Add the new version to the `python` matrix under the `test` job strategy
248-
- Ensure the new version is included in integration test runs (typically the latest version should be tested)
250+
- Add the new version to the `unit-tests` job matrix (all Python versions run on Ubuntu; only one version per macOS/Windows)
251+
- Add the new version to the `integration-tests` job matrix if it should be integration-tested (typically the oldest and newest supported versions)
249252
- Update any Python version comments or documentation within the workflow
250253
251254
**Testing:**
@@ -269,7 +272,7 @@ When dropping support for an old Python version (e.g., removing Python 3.10), up
269272
270273
**CI/CD configuration files:**
271274
1. **`.github/workflows/build.yml`**:
272-
- Remove the old version from the `python` matrix under the `test` job strategy
275+
- Remove the old version from the `unit-tests` and `integration-tests` job matrices
273276
- Update the cache key version (e.g., increment `v28` to `v29`) to invalidate old caches
274277
275278
**Documentation:**
@@ -315,10 +318,10 @@ available at runtime:
315318
1. Update the examples in the docstring to remove the await or async function calls.
316319
1. Import the protocol class you created and add it to the class constructor to
317320
inherit the protocol class.
318-
1. Write unit and integration tests for BOTH the async and non-async versions.
319-
1. Write your tests once with async in mind.
320-
1. Copy them to a non-async testing directory.
321-
1. Remove the async-related keywords and imports.
321+
1. Write unit and integration tests for the **async** version only.
322+
1. The `@async_to_sync` decorator automatically generates the sync wrapper at runtime.
323+
1. The sync wrapper is covered by dedicated unit tests in `tests/unit/synapseclient/core/unit_test_async_to_sync.py` and a smoke integration test in `tests/integration/synapseclient/models/synchronous/test_sync_wrapper_smoke.py`.
324+
1. **Do not** create duplicate synchronous test files.
322325
1. Add the method definitions to the appropriate markdown file for generated doc pages.
323326
324327
##### Creating a new async method to be called internally by the client
@@ -331,8 +334,9 @@ generation of non-async code.
331334
332335
##### Modifying an existing async method
333336
When you make a modification to an async method please also copy any changes to the
334-
definition of the method OR docstring into the non-async method defintion. It is
335-
expected that you manually keep them in-sync.
337+
definition of the method OR docstring into the non-async Protocol class definition. The
338+
sync wrapper is generated automatically by the `@async_to_sync` decorator, so only the
339+
Protocol class (used for static type checking) needs to be updated manually.
336340
337341
### Code style
338342
@@ -428,12 +432,18 @@ April 2024 it was found that during a single run of all integration tests almost
428432
connections were created and subsequently closed during the test run. As such the
429433
following set of guidelines should be followed:
430434
431-
- All tests should use the `async` keyword. This allows any tests to share the
432-
underlying HTTPX async client for requests.
433-
- Any non `session` scoped fixtures should not execute an HTTP request. If the fixture
434-
does need to execute a request it should not be scoped to `function`. This is because
435-
each scope level runs it's own event loop; Connection pools cannot be shared between
436-
each of the event loops.
435+
- **Test function signatures:**
436+
- Tests in `tests/integration/synapseclient/models/async/` should use `async def` to test async methods
437+
- A single sync wrapper smoke test in `tests/integration/synapseclient/models/synchronous/test_sync_wrapper_smoke.py` covers the `@async_to_sync` decorator against the real API. **Do not** create additional per-model synchronous integration test files.
438+
- **Important (Python 3.14+):** Synchronous tests must NOT use `async def`, as this creates an active event loop that prevents synchronous methods from working correctly. The sync smoke test is skipped on Python 3.14+.
439+
- **Fixtures:** Can be `async def` if they need to call async methods, but should use regular `def` when calling synchronous methods.
440+
- **Fixture scoping guidelines:**
441+
- `session` scope: Use for the Synapse client (`syn`), shared project (`project_model`), and `schedule_for_cleanup`. These are created once per test session/worker.
442+
- `class` scope: Use for expensive container entities (Projects, Evaluations, Tables, Folders with files) that are **not mutated** by tests — only used as parents or read-only containers. This avoids redundant entity creation across tests within a class.
443+
- `function` scope: Use for entities that tests **mutate** (e.g., files with changed names, datasets with added/removed items, submission statuses being updated). Each test gets a fresh entity.
444+
- All fixtures that create Synapse entities **must** call `schedule_for_cleanup()` to register them for cleanup at session end.
445+
- **Polling and retries:** For eventual-consistency scenarios (e.g., waiting for permission propagation, schema binding, attachment preview generation), use `wait_for_condition()` from `tests/integration/helpers.py` instead of hardcoded `asyncio.sleep()` calls. This uses exponential backoff and returns as soon as the condition is met.
446+
- **Parallel execution:** Tests run with `pytest -n 8 --dist loadscope`, which ensures all tests in a class execute on the same worker sequentially. Session-scoped fixtures are shared within each worker.
437447
438448
### Repository Admins
439449

0 commit comments

Comments
 (0)