From 5a426abeaf620692fc609521df4492a24cfcede4 Mon Sep 17 00:00:00 2001 From: 73junito <73junito@gmail.com> Date: Tue, 30 Dec 2025 12:49:59 -0600 Subject: [PATCH 1/2] tests: fix Windows tempfile re-open by using delete=False and explicit cleanup --- tests/test_client.py | 52 ++++++++++++++++++++++---------- tests/test_type_serialization.py | 17 ++++++++--- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 24a8af3..6eae460 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -459,13 +459,16 @@ def test_client_generate_images(httpserver: HTTPServer): ) client = Client(httpserver.url_for('/')) - - with tempfile.NamedTemporaryFile() as temp: + with tempfile.NamedTemporaryFile(delete=False) as temp: temp.write(PNG_BYTES) temp.flush() - response = client.generate('dummy', 'Why is the sky blue?', images=[temp.name]) + temp_name = temp.name + try: + response = client.generate('dummy', 'Why is the sky blue?', images=[temp_name]) assert response['model'] == 'dummy' assert response['response'] == 'Because it is.' + finally: + os.unlink(temp_name) def test_client_generate_format_json(httpserver: HTTPServer): @@ -740,20 +743,26 @@ def test_client_create_blob(httpserver: HTTPServer): httpserver.expect_ordered_request(re.compile('^/api/blobs/sha256[:-][0-9a-fA-F]{64}$'), method='POST').respond_with_response(Response(status=201)) client = Client(httpserver.url_for('/')) - - with tempfile.NamedTemporaryFile() as blob: - response = client.create_blob(blob.name) + with tempfile.NamedTemporaryFile(delete=False) as blob: + blob_name = blob.name + try: + response = client.create_blob(blob_name) assert response == 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + finally: + os.unlink(blob_name) def test_client_create_blob_exists(httpserver: HTTPServer): httpserver.expect_ordered_request(PrefixPattern('/api/blobs/'), method='POST').respond_with_response(Response(status=200)) client = Client(httpserver.url_for('/')) - - with tempfile.NamedTemporaryFile() as blob: - response = client.create_blob(blob.name) + with tempfile.NamedTemporaryFile(delete=False) as blob: + blob_name = blob.name + try: + response = client.create_blob(blob_name) assert response == 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + finally: + os.unlink(blob_name) def test_client_delete(httpserver: HTTPServer): @@ -945,13 +954,16 @@ async def test_async_client_generate_images(httpserver: HTTPServer): ) client = AsyncClient(httpserver.url_for('/')) - - with tempfile.NamedTemporaryFile() as temp: + with tempfile.NamedTemporaryFile(delete=False) as temp: temp.write(PNG_BYTES) temp.flush() - response = await client.generate('dummy', 'Why is the sky blue?', images=[temp.name]) + temp_name = temp.name + try: + response = await client.generate('dummy', 'Why is the sky blue?', images=[temp_name]) assert response['model'] == 'dummy' assert response['response'] == 'Because it is.' + finally: + os.unlink(temp_name) async def test_async_client_pull(httpserver: HTTPServer): @@ -1118,9 +1130,13 @@ async def test_async_client_create_blob(httpserver: HTTPServer): client = AsyncClient(httpserver.url_for('/')) - with tempfile.NamedTemporaryFile() as blob: - response = await client.create_blob(blob.name) + with tempfile.NamedTemporaryFile(delete=False) as blob: + blob_name = blob.name + try: + response = await client.create_blob(blob_name) assert response == 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + finally: + os.unlink(blob_name) async def test_async_client_create_blob_exists(httpserver: HTTPServer): @@ -1128,9 +1144,13 @@ async def test_async_client_create_blob_exists(httpserver: HTTPServer): client = AsyncClient(httpserver.url_for('/')) - with tempfile.NamedTemporaryFile() as blob: - response = await client.create_blob(blob.name) + with tempfile.NamedTemporaryFile(delete=False) as blob: + blob_name = blob.name + try: + response = await client.create_blob(blob_name) assert response == 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + finally: + os.unlink(blob_name) async def test_async_client_delete(httpserver: HTTPServer): diff --git a/tests/test_type_serialization.py b/tests/test_type_serialization.py index f458cd2..745920d 100644 --- a/tests/test_type_serialization.py +++ b/tests/test_type_serialization.py @@ -1,4 +1,5 @@ import tempfile +import os from base64 import b64encode from pathlib import Path @@ -32,19 +33,27 @@ def test_image_serialization_plain_string(): def test_image_serialization_path(): - with tempfile.NamedTemporaryFile() as temp_file: + with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_file.write(b'test file content') temp_file.flush() - img = Image(value=Path(temp_file.name)) + temp_name = temp_file.name + try: + img = Image(value=Path(temp_name)) assert img.model_dump() == b64encode(b'test file content').decode() + finally: + os.unlink(temp_name) def test_image_serialization_string_path(): - with tempfile.NamedTemporaryFile() as temp_file: + with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_file.write(b'test file content') temp_file.flush() - img = Image(value=temp_file.name) + temp_name = temp_file.name + try: + img = Image(value=temp_name) assert img.model_dump() == b64encode(b'test file content').decode() + finally: + os.unlink(temp_name) with pytest.raises(ValueError): img = Image(value='some_path/that/does/not/exist.png') From 53ef1364aa9cf53142f34ab5c78c4d4a4cb45e81 Mon Sep 17 00:00:00 2001 From: 73junito <73junito@gmail.com> Date: Tue, 30 Dec 2025 13:47:17 -0600 Subject: [PATCH 2/2] ci: add matrix workflow including windows to catch platform-specific test flakes --- .github/workflows/ci-matrix.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/ci-matrix.yml diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml new file mode 100644 index 0000000..c06ab0a --- /dev/null +++ b/.github/workflows/ci-matrix.yml @@ -0,0 +1,45 @@ +name: CI Matrix (including Windows) + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: [3.11] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt || true + - name: Run tests + run: | + # ensure a project-local temp dir to avoid AppData permission issues on Windows + python - <<'PY' +import os, pathlib +p = pathlib.Path('tmp') +p.mkdir(exist_ok=True) +os.environ['TMP'] = str(p.resolve()) +os.environ['TEMP'] = str(p.resolve()) +print('TMP/TEMP set to', os.environ['TMP']) +PY + pytest --basetemp=tmp/pytest --cov --cov-branch --cov-report=xml + - name: Upload coverage to Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-xml-${{ matrix.os }} + path: coverage.xml