Skip to content

chore(ci): add Windows compatibility testing#13049

Open
sjpadgett wants to merge 10 commits into
openemr:masterfrom
sjpadgett:windows-actions
Open

chore(ci): add Windows compatibility testing#13049
sjpadgett wants to merge 10 commits into
openemr:masterfrom
sjpadgett:windows-actions

Conversation

@sjpadgett

Copy link
Copy Markdown
Member

Run isolated tests on a native Windows runner to detect filesystem, path, filename, and NTFS case-sensitivity problems not covered by Linux CI. Plans are to add to this.

Use PHP 8.2 so the job tests OpenEMR's minimum supported version without duplicating the existing PHP version matrix.

Assisted-by: ChatGPT Code

Changes proposed in this pull request:

add workflow

Was an AI assistant used? Yes

Run isolated tests on a native Windows runner to detect filesystem, path, filename, and NTFS case-sensitivity problems not covered by Linux CI.

Use PHP 8.2 so the job tests OpenEMR's minimum supported version without duplicating the existing PHP version matrix.

Assisted-by: ChatGPT
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Windows GitHub Actions workflow for path validation, PHPUnit execution, JUnit reporting, and workspace checks; updates FHIR export cleanup and group UUID validation; and aligns PHPStan baseline entries.

Changes

Windows compatibility workflow

Layer / File(s) Summary
Windows runner and tool setup
.github/workflows/windows-tests.yml
Defines triggers, permissions, concurrency, environment defaults, Windows runner settings, Git checkout configuration, PHP 8.2, Composer, and diagnostics.
Tracked path safety validation
.github/workflows/windows-tests.yml
Detects case-insensitive path collisions and Windows-incompatible tracked names before testing.
Test execution and workspace verification
.github/workflows/windows-tests.yml
Runs PHPUnit with isolated configuration, reports and uploads JUnit output, and fails if the test run modifies the repository.

FHIR export safeguards

Layer / File(s) Summary
Export cleanup and group UUID validation
src/RestControllers/FHIR/Operations/FhirOperationExportRestController.php
Conditionally closes the export writer, rejects empty group identifiers, and excludes empty extracted UUIDs.

PHPStan baseline updates

Layer / File(s) Summary
Align PHPStan diagnostics and suppressions
.phpstan/baseline/*.php
Removes obsolete suppressions and baseline includes, narrows an offset diagnostic, and updates Tree diagnostic counts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant WindowsRunner
  participant PHPUnit
  participant ArtifactStorage
  GitHubActions->>WindowsRunner: checkout and configure PHP 8.2
  WindowsRunner->>WindowsRunner: validate tracked paths
  WindowsRunner->>PHPUnit: run isolated suite
  PHPUnit-->>WindowsRunner: write JUnit XML
  WindowsRunner->>ArtifactStorage: upload JUnit artifact
Loading

Possibly related PRs

Suggested labels: PHP

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding Windows compatibility testing in CI.
Description check ✅ Passed The description is directly related to the new Windows test workflow and its PHP 8.2 compatibility goals.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/windows-tests.yml:
- Line 79: Disable Git path quoting in both path-list retrieval sites: update
the ls-files invocation assigned to $paths and the ls-files invocation iterated
by the foreach loop in .github/workflows/windows-tests.yml at lines 60-60 and
79-79 to use core.quotePath=false, preserving raw readable paths for grouping,
logging, and validation.
- Around line 38-41: Update the actions/checkout step in the Windows test
workflow to set persist-credentials to false alongside fetch-depth, preventing
the GitHub token from being stored in the local Git configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 464706a8-eee2-419e-8ea3-3598f18bf904

📥 Commits

Reviewing files that changed from the base of the PR and between 67f4357 and 0d05ee1.

📒 Files selected for processing (1)
  • .github/workflows/windows-tests.yml

Comment thread .github/workflows/windows-tests.yml Outdated
Comment thread .github/workflows/windows-tests.yml Outdated
jobs:
windows-compatibility:
name: Windows compatibility
runs-on: windows-2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as the kids say…

"Let's goooooo!"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep I need some hand holding in this space

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.00000% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.58%. Comparing base (a59f31d) to head (adb4466).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...R/Operations/FhirOperationExportRestController.php 48.64% 19 Missing ⚠️
src/Services/FHIR/UtilsService.php 0.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #13049      +/-   ##
============================================
- Coverage     27.47%   25.58%   -1.89%     
- Complexity    85472    85478       +6     
============================================
  Files          3943     3943              
  Lines        422409   422167     -242     
============================================
- Hits         116041   108022    -8019     
- Misses       306368   314145    +7777     
Flag Coverage Δ
apache 15.36% <45.00%> (+<0.01%) ⬆️
api 3.87% <51.42%> (+<0.01%) ⬆️
api-tests 3.87% <51.42%> (+<0.01%) ⬆️
e2e 8.79% <0.00%> (-0.01%) ⬇️
e2e-tests 8.79% <0.00%> (-0.01%) ⬇️
email 0.18% <0.00%> (ø)
http ?
inferno ?
isolated-php8.2 9.56% <0.00%> (-0.01%) ⬇️
isolated-php8.3 9.56% <0.00%> (-0.01%) ⬇️
isolated-php8.4 9.56% <0.00%> (-0.01%) ⬇️
isolated-php8.5 9.56% <0.00%> (-0.01%) ⬇️
isolated-php8.6 9.56% <0.00%> (-0.01%) ⬇️
mariadb11.8.8 15.36% <45.00%> (+<0.01%) ⬆️
php8.2 15.36% <45.00%> (?)
php8.5 ?
phpunit ?
services 5.33% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sjpadgett

Copy link
Copy Markdown
Member Author

I'm looking into some other gotcha area concerning windows I've run across over the year.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/windows-tests.yml:
- Around line 107-111: Remove the permissive if conditions from the Windows
workflow steps at .github/workflows/windows-tests.yml lines 107-111, 131, and
140. Delete always() from the “Report isolated-suite Windows baseline” step and
!cancelled() from the artifact-warning and checkout-related steps, allowing the
default success() condition to prevent them from running after setup or
validation failures while preserving normal execution after the
continue-on-error test step.
- Line 131: Remove the explicit if condition from the artifact upload step in
the Windows workflow. Let the default success() condition govern execution so
uploads still occur after the continue-on-error windows-tests step fails, but
are skipped when earlier workflow steps abort before tests run.
- Line 140: Remove the explicit if condition from the cleanup check in the
Windows workflow, allowing the default success() condition to control execution.
Preserve the cleanup step and its behavior, including execution after
continue-on-error test failures while skipping it when an earlier validation
step fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c1a745be-4aca-4d91-a4df-e9b3245dab14

📥 Commits

Reviewing files that changed from the base of the PR and between 0d05ee1 and ba0f784.

📒 Files selected for processing (1)
  • .github/workflows/windows-tests.yml

Comment thread .github/workflows/windows-tests.yml Outdated
Comment thread .github/workflows/windows-tests.yml Outdated
Comment thread .github/workflows/windows-tests.yml Outdated
retention-days: 14

- name: Verify tests left the checkout clean
if: ${{ !cancelled() }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove if: ${{ !cancelled() }} to prevent evaluating an invalid checkout state on early workflow failures.

For the same reason as the artifact upload step, the default success() condition is sufficient to run this cleanup check even if the continue-on-error test suite fails. Using !cancelled() inappropriately executes this check if an early validation step fails, potentially evaluating an incomplete or broken checkout state.

♻️ Proposed fix
     - name: Verify tests left the checkout clean
-      if: ${{ !cancelled() }}
       shell: pwsh
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: ${{ !cancelled() }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/windows-tests.yml at line 140, Remove the explicit if
condition from the cleanup check in the Windows workflow, allowing the default
success() condition to control execution. Preserve the cleanup step and its
behavior, including execution after continue-on-error test failures while
skipping it when an earlier validation step fails.

…e skipped.

Non-blocking PHPUnit failure → summary and JUnit artifact still run.
Successful PHPUnit run → reports passed.
Clean-checkout verification runs only when the preceding required steps succeeded.
coderabbitai review fixes. he makes it too easy!
… group validation

This may help some tests failure rate.
- Initialize \$exportWriter to null before try block to prevent undefined variable errors in finally block
- Add null check in finally block before accessing \$exportWriter methods
- Add early validation in getPatientUuidsForGroup() to fail fast on empty group IDs
- Fixes timeout issue in GroupExportFhirApiTest::testGroupExportWithNonExistingGroupId
- Improves error handling and prevents resource exhaustion from hanging finally blocks"

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/RestControllers/FHIR/Operations/FhirOperationExportRestController.php`:
- Line 738: Update the UUID filter in the export flow around
UtilsService::getUuidFromReference() so it explicitly rejects null and only
appends non-empty UUID strings to $patientUuids. Preserve the existing
collection behavior for valid extracted UUIDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e0dfa75-40d2-43bf-b8e8-ca364d3c8ef8

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0be51 and d7cb373.

📒 Files selected for processing (1)
  • src/RestControllers/FHIR/Operations/FhirOperationExportRestController.php

Comment thread src/RestControllers/FHIR/Operations/FhirOperationExportRestController.php Outdated
@sjpadgett

Copy link
Copy Markdown
Member Author

@bradymiller @kojiromike I'm done if you want to look over.

@sjpadgett

Copy link
Copy Markdown
Member Author

okay @bradymiller I've dabbled in testing now so can I get back to being a developer and leave tests to my intern, Claude and Goober(ChatGPT and I think it takes offence to the name!)? lol

@kojiromike kojiromike left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding Windows compatibility coverage — the workflow is well put together (concurrency block, least-privilege permissions, persist-credentials: false, XDEBUG_MODE: off, and the case-collision / reserved-name detectors are a nice portable safety net). The core FHIR fix (initializing $exportWriter and guarding the finally) is correct too.

A couple of code issues to fix first — see coderabbit's on the $uuid check (a null regression) and mine on the empty($groupId) guard.

On the .phpstan/baseline/ changes: they're tangled (a dropped forbiddenEval include plus unrelated Tree.class.php drift). Rather than hand-edit, please fix the code issues above, then regenerate the baseline from scratch (composer phpstan-baseline-reset) so the diff reflects only what this PR actually changes.

Comment thread .github/workflows/windows-tests.yml Outdated
- name: Setup PHP 8.2 and Composer
uses: ./.github/actions/setup-php-composer
with:
# Linux CI owns PHP-version coverage; Windows is the variable here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this comment is indented 10 spaces while php-version: below it is at 8 — valid YAML but visually misaligned. Align the comment with the keys it documents.

Suggested change
# Linux CI owns PHP-version coverage; Windows is the variable here.
# Linux CI owns PHP-version coverage; Windows is the variable here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

baseline came from ci generated baseline in test in last commit. I went through at least four conflicts from other merged PRs. I think all were phpstan issues.
I put together a new PR commit process using pre-commits and emulating upstream more closely so hopefully next PR I will test and get it figured out.

@@ -729,7 +735,7 @@ private function getPatientUuidsForGroup($groupId)
foreach ($group->getMember() as $member) {
if (!empty($member->getEntity()) && !empty($member->getEntity()->getReference())) {
$uuid = UtilsService::getUuidFromReference($member->getEntity());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coderabbit's comment below is right, but I'd fix it at the source…

Suggested change
$uuid = UtilsService::getUuidFromReference($member->getEntity());
$uuid = UtilsService::getUuidFromReference($member->getEntity()) ?? '';

Comment thread src/RestControllers/FHIR/Operations/FhirOperationExportRestController.php Outdated
Comment on lines +726 to +728
if (empty($groupId)) {
throw new \InvalidArgumentException("Group ID cannot be empty");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message says this fixes testGroupExportWithNonExistingGroupId, but a non-existing group ID is a well-formed, non-empty value — it passes this guard and proceeds to getOne(), which returns no data and yields []. So this only guards genuinely empty input, not the non-existing case described. Also worth confirming the caller catches InvalidArgumentException, otherwise this surfaces as a 500 rather than a clean OperationOutcome.

Also, please avoid empty(). It's an old and broken php function that we shouldn't use anymore.

Suggested change
if (empty($groupId)) {
throw new \InvalidArgumentException("Group ID cannot be empty");
}
if ($groupId === null || $groupId === '') {
throw new \InvalidArgumentException("Group ID cannot be empty");
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to fix coderabbit last review this morning because it got to late for me last night. Thanks for reminder and review.

@sjpadgett

Copy link
Copy Markdown
Member Author

Looks like Bulk EHI is broken. I have to setup debug mode to track. Haven't test on easy dev yet. I haven't touched any of this modules source. If anyone wants to test on a release demo that be helpful because I have to finish the export FHIR endpoint which I have touched that code in this PR.
I prob should have left but I hoped to get the test fail rate down! No good deed etc ect ... lol

image

Half done. Still need to fetch the report.

image

Make UtilsService::getUuidFromReference() null-tolerant (accept
?FHIRReference and return null for a null reference), since FHIR
getters can return null despite non-null @var annotations on the
generated R4 classes. Group-member entity resolution then drops its
now-redundant null guards and relies on the utility plus the existing
empty-UUID filter.

Replace ambiguous empty() checks with explicit typed comparisons,
reject null or empty group IDs, and tighten resource collection types
and comparisons. Align the OpenAPI named arguments with their declared
parameter order.

Verified that processExport() catches InvalidArgumentException and
returns a 400 Bad Request OperationOutcome, so the group ID guard does
not introduce 400-to-500 response drift. A well-formed but nonexistent
group ID continues through the existing no-members path.

Assisted-by: ChatGPT Work
Assisted-by: Claude
@sjpadgett

Copy link
Copy Markdown
Member Author

We've been all over this workflow and believe we've narrowed in but will prove tomorrow. I'll just have to use docker. Tried hard to avoid but if I can prove then we can communicate to others in contrib.

Status: PHPStan baseline on native Windows

The PR's actual code change is clean — phpstan-analyze-staged consistently reports a single expected change: the 'system/' . mixed count in binaryOp.invalid.php drops from 2 → 1 (one usage removed in FhirOperationExportRestController). That one-line baseline decrement is applied.

The gotcha we hit: running the documented composer phpstan-baseline full regen on a native Windows host produces a divergent baseline — a wall of added entries under the custom openemr.noGlobalNsFunctions and openemr.forbidDirectSessionWrite rules (~560+) that CI's Linux baseline doesn't contain. Committing that regen would fail the git diff --exit-code .phpstan/baseline/ guard. Two contributing factors identified: (1) interrupted regens (local OOM) left orphaned, unreferenced baseline files on disk that made every already-baselined error report as "new," and (2) even on a clean tree, the full regen still diverges — pointing at Windows path/namespace resolution in those two custom rules.

Resolution used: surgical hand-edit of the single real baseline change, letting CI (Linux, correct PHP matrix) validate.
How we'll prove it: regenerate the baseline inside the Linux dev container (openemr-cmd / Docker path per CONTRIBUTING) on the same clean tree and confirm the diff is only the binaryOp.invalid.php 2→1 change — no noGlobalNsFunctions/forbidDirectSessionWrite churn. If the container regen is clean while the native-Windows regen isn't, that confirms the divergence is host-environmental, not a code issue — and is worth a doc note that composer phpstan-baseline must be run in the container, not on a native Windows host.

@sjpadgett

sjpadgett commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

Need a comment in contrib maybe
PHPStan baseline regeneration is not reliable on native Windows
Running composer phpstan-baseline on a native Windows host (outside the Linux dev container) produces a baseline that diverges from the one CI generates, causing the git diff --exit-code .phpstan/baseline/ check to fail even when your code changes are correct.
The divergence shows up as large spurious additions under the project's custom rules — notably openemr.noGlobalNsFunctions and openemr.forbidDirectSessionWrite — that are not present in CI's Linux-generated baseline. These custom rules resolve differently on Windows, so a Windows-regenerated baseline does not match CI's.
Regenerate the baseline inside the Linux dev container (the Docker openemr-cmd environment), not on a native Windows host. The container matches CI's environment and produces a baseline CI will accept.
One related pitfall: an interrupted regeneration (e.g. a local out-of-memory crash) can leave untracked baseline files in .phpstan/baseline/ that loader.php doesn't reference. These cause already-baselined errors to report as new. Check git status for stray untracked .phpstan/baseline/*.php files and remove them before trusting a local run.

Need input!

I'm going to publish ot forum and elsewhere a how to for alternative to xampp. Two option of WSL2 or Docker for Windows.

btw: I need put up PR for issue #13054 and will use docker going forward. I had to try to make windows work so thanks for patience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants