From e454e82d412fd8673e82cba8e1a334ff295b344e Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 09:55:02 +0200 Subject: [PATCH 1/7] Run Physical's tests on macOS and on Linux on Arm in CI Physical calls the C library itself and its rules differ by system, but its tests ran only on Linux x64 and Windows: the folder delete of #70 will use flags whose values differ by system and architecture. The new jobs build and run its tests on macos-latest and ubuntu-24.04-arm. Refs #73 --- .github/workflows/ci.yml | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f545e67..b105397 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,8 @@ # Build, test with coverage, and pack, on every pull request and every push to main. The emulator suites start their # containers through Testcontainers, which needs Docker: present on the Linux runner, where WFP_REQUIRE_DOCKER makes a # missing emulator fail the run instead of leaving its tests inconclusive. Live suites (TestCategory=Live) never run here. -# The Native AOT job publishes tests/WriteableFileProviders.Aot with native AOT and runs it (its README says why). +# The Native AOT job publishes tests/WriteableFileProviders.Aot with native AOT and runs it (its README says why). The +# Physical jobs run the local disk provider's tests on macOS and on Linux on Arm, which the others don't reach. name: CI on: @@ -166,3 +167,32 @@ jobs: # No Linux containers on Windows runners: the emulator suites are inconclusive here and run on Linux. - name: Test run: dotnet test --solution WriteableFileProviders.slnx --no-build -c Release --filter "TestCategory!=Live" + + physical: + # Physical calls the C library itself, and its rules differ by system (case, names, links, permissions): its tests + # run where the jobs above don't, on macOS and on Linux on Arm. + name: Physical (${{ matrix.name }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - name: macOS + runner: macos-latest + - name: Linux, Arm64 + runner: ubuntu-24.04-arm + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 + with: + global-json-file: global.json + - name: Restore (locked) + run: dotnet restore tests/WriteableFileProviders.Physical.Tests --locked-mode + - name: Build + run: dotnet build tests/WriteableFileProviders.Physical.Tests --no-restore -c Release + - name: Test + run: dotnet test --project tests/WriteableFileProviders.Physical.Tests --no-build -c Release --filter "TestCategory!=Live" From a3799e03a89399be50bf12593da52a713b260584 Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 10:00:12 +0200 Subject: [PATCH 2/7] Count the links in a test's chain as macOS does, and say where CI runs Physical's tests On macOS the temp folder is under /var, a link to /private/var, so a chain of exactly the link limit made there passed one link more than the test counted, and the kernel would refuse it too: the test now makes its chains in the folder's physical path. AGENTS.md, REVIEW.md, CONTRIBUTING.md and release.yml's comments name the new jobs. Refs #73 --- .github/workflows/release.yml | 7 ++++--- AGENTS.md | 8 +++++--- CONTRIBUTING.md | 3 ++- REVIEW.md | 5 +++-- .../PhysicalProviderTests.cs | 11 +++++++---- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7aa517..798f21c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,9 @@ # The release: pushing a tag v on main builds, tests with coverage and the coverage gate, and packs every # package at (MinVer reads the tag), checks them, pushes them to nuget.org and creates the GitHub release with # the packages attached and the version's section of CHANGELOG.md as its notes. Nothing is pushed unless ci.yml -# concluded success on the tagged commit, which covers what this workflow does not run (the Windows build and tests). A -# tag with a prerelease suffix (v2027.1.0-rc.1) makes a prerelease on both. docs/releasing.md is the runbook. +# concluded success on the tagged commit, which covers what this workflow does not run (the Windows build and tests, and +# Physical's tests on macOS and on Linux on Arm). A tag with a prerelease suffix (v2027.1.0-rc.1) makes a prerelease on +# both. docs/releasing.md is the runbook. # # Setup: docs/releasing.md. No long-lived API key exists anywhere: NuGet/login trades the job's OIDC token for a key that # lives one hour. @@ -108,7 +109,7 @@ jobs: actions: read steps: # ci.yml runs on every push to main, so the tagged commit has a run of its own: it must have concluded success, - # Windows job included, before anything is pushed. A run still going (a tag pushed right after the merge) is + # its Windows and Physical jobs included, before anything is pushed. A run still going (a tag pushed right after the merge) is # waited for; a run that failed, or none at all, stops the release. Re-run this workflow once CI is green. - name: Require a successful ci.yml run env: diff --git a/AGENTS.md b/AGENTS.md index 424e9b1..a33fe31 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,10 +80,12 @@ Run everything from the repo root, with a .NET 10 SDK (`global.json`). Docker is available and a missing emulator fails the run (`WFP_REQUIRE_DOCKER=1`), and the build and tests on Windows. Its Native AOT job publishes `tests/WriteableFileProviders.Aot` with native AOT, which fails on any trim or AOT warning in the packages or the SDKs they call (but one, its README says which), and runs it on the local disk, - the gateway, Azurite, MinIO and the Graph fake. + the gateway, Azurite, MinIO and the Graph fake. Its Physical jobs build and run Physical's tests on macOS and on Linux + on Arm64. - **Release:** a maintainer tags `v` on `main`. `release.yml` builds, runs the tests with coverage and the coverage gate, packs, checks that every package has that version and depends on our other packages within its major - (`eng/package-check.cs`), and requires that `ci.yml` concluded success on the tagged commit (its Windows job included); + (`eng/package-check.cs`), and requires that `ci.yml` concluded success on the tagged commit (its Windows and Physical + jobs included); then, once a maintainer approves the `nuget` environment, it pushes the packages to nuget.org through trusted publishing and creates the GitHub release, with the version's section of `CHANGELOG.md` as its notes. [docs/releasing.md](docs/releasing.md) has the steps. @@ -124,7 +126,7 @@ Each package has a test project of its own, `tests/WriteableFileProviders.`. Its title becomes the commit on `main`. -CI builds and tests every PR on Linux and Windows, with the emulator suites and the coverage gate, and CodeQL scans it. +CI builds and tests every PR on Linux and Windows, with the emulator suites and the coverage gate, runs Physical's tests +on macOS and on Linux on Arm64 too, and CodeQL scans it. The maintainers review a PR from outside by reading it: they never check it out, build or run it on their machines, where it would run next to their credentials. Your PR is ready for review when CI is green. A maintainer squash-merges it. diff --git a/REVIEW.md b/REVIEW.md index faefe35..7142277 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -8,8 +8,9 @@ acceptable. 1. **Build and tests.** Is CI green on the PR's head commit (`AGENTS.md`, Build and test)? `ci.yml` builds the solution, runs every test outside the `Live` category on Linux, where Docker runs the emulator suites and a missing - emulator fails the run, and on Windows, then runs the coverage gate and packs, and its Native AOT job publishes - `tests/WriteableFileProviders.Aot` with native AOT and runs it; `codeql.yml` scans the code and the workflows. CI + emulator fails the run, and on Windows, then runs the coverage gate and packs, its Native AOT job publishes + `tests/WriteableFileProviders.Aot` with native AOT and runs it, and its Physical jobs run Physical's tests on macOS and + on Linux on Arm64; `codeql.yml` scans the code and the workflows. CI never runs the live suites against real accounts: for a change to a provider, does the PR say whether its live suite ran, and if it did not, why? For a PR from outside the maintainers, the answer comes from CI, never from running its code on a maintainer's machine, so a live suite it needs runs only once the change is on `main`, diff --git a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs index 080932e..6d2374b 100644 --- a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs +++ b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs @@ -591,8 +591,11 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.AreEqual(WriteablePhysicalFileProvider.PhysicalPathOf(folder), WriteablePhysicalFileProvider.PhysicalPathOf(folder + Path.DirectorySeparatorChar)); Assert.AreEqual(Path.Combine(Path.GetDirectoryName(physicalFile)!, "missing.txt"), WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "missing.txt"))); - // Chains of links: one through exactly the limit resolves, one link more does not (32 on macOS, 40 elsewhere). + // Chains of links: one through exactly the limit resolves, one link more does not (32 on macOS, 40 elsewhere). They + // are made in the folder as the kernel finds it, so that no link of its own path counts: on macOS the temp folder + // is under /var, a link to /private/var. Assert.AreEqual(OperatingSystem.IsMacOS() ? 32 : 40, WriteablePhysicalFileProvider.MaxLinks); + var physicalFolder = Path.GetDirectoryName(physicalFile)!; try { Chain("a", WriteablePhysicalFileProvider.MaxLinks); @@ -602,14 +605,14 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.Inconclusive($"Creating a symbolic link on Windows needs Developer Mode or elevation: {e.Message}"); } Chain("b", WriteablePhysicalFileProvider.MaxLinks + 1); - Assert.AreEqual(physicalFile, WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "a0"))); - Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "b0"))); + Assert.AreEqual(physicalFile, WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "a0"))); + Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "b0"))); // prefix0 -> prefix1 -> ... -> prefix(count - 1) -> file.txt: a path through count links. void Chain(string prefix, int count) { for (var i = 0; i < count; i++) - File.CreateSymbolicLink(Path.Combine(folder, $"{prefix}{i}"), i == count - 1 ? "file.txt" : $"{prefix}{i + 1}"); + File.CreateSymbolicLink(Path.Combine(physicalFolder, $"{prefix}{i}"), i == count - 1 ? "file.txt" : $"{prefix}{i + 1}"); } } From 693c25e919f1ef8bdf9a7e85dd90c6b952db1e44 Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 11:25:12 +0200 Subject: [PATCH 3/7] Pin the link limit on the kernel, and name every job release.yml relies on The link-chain test now also reads through the kernel: a chain of exactly the limit opens and one link more fails, which the macOS job checks against Darwin's 32 and Linux's against 40. release.yml and AGENTS.md name the Native AOT job among those a release needs green; Arm64 is said throughout, and the paragraphs the change touched are rewrapped. Refs #73 --- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 11 ++++++----- AGENTS.md | 12 ++++++------ REVIEW.md | 11 +++++------ .../PhysicalProviderTests.cs | 15 ++++++++++----- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b105397..5da11b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ # containers through Testcontainers, which needs Docker: present on the Linux runner, where WFP_REQUIRE_DOCKER makes a # missing emulator fail the run instead of leaving its tests inconclusive. Live suites (TestCategory=Live) never run here. # The Native AOT job publishes tests/WriteableFileProviders.Aot with native AOT and runs it (its README says why). The -# Physical jobs run the local disk provider's tests on macOS and on Linux on Arm, which the others don't reach. +# Physical jobs run the local disk provider's tests on macOS and on Linux on Arm64, which the others don't reach. name: CI on: @@ -170,7 +170,7 @@ jobs: physical: # Physical calls the C library itself, and its rules differ by system (case, names, links, permissions): its tests - # run where the jobs above don't, on macOS and on Linux on Arm. + # run where the jobs above don't, on macOS and on Linux on Arm64. name: Physical (${{ matrix.name }}) runs-on: ${{ matrix.runner }} timeout-minutes: 20 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 798f21c..d4d3718 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,9 @@ # The release: pushing a tag v on main builds, tests with coverage and the coverage gate, and packs every # package at (MinVer reads the tag), checks them, pushes them to nuget.org and creates the GitHub release with # the packages attached and the version's section of CHANGELOG.md as its notes. Nothing is pushed unless ci.yml -# concluded success on the tagged commit, which covers what this workflow does not run (the Windows build and tests, and -# Physical's tests on macOS and on Linux on Arm). A tag with a prerelease suffix (v2027.1.0-rc.1) makes a prerelease on -# both. docs/releasing.md is the runbook. +# concluded success on the tagged commit, which covers what this workflow does not run (the Windows build and tests, the +# Native AOT app, and Physical's tests on macOS and on Linux on Arm64). A tag with a prerelease suffix (v2027.1.0-rc.1) +# makes a prerelease on both. docs/releasing.md is the runbook. # # Setup: docs/releasing.md. No long-lived API key exists anywhere: NuGet/login trades the job's OIDC token for a key that # lives one hour. @@ -109,8 +109,9 @@ jobs: actions: read steps: # ci.yml runs on every push to main, so the tagged commit has a run of its own: it must have concluded success, - # its Windows and Physical jobs included, before anything is pushed. A run still going (a tag pushed right after the merge) is - # waited for; a run that failed, or none at all, stops the release. Re-run this workflow once CI is green. + # its Windows, Native AOT and Physical jobs included, before anything is pushed. A run still going (a tag pushed + # right after the merge) is waited for; a run that failed, or none at all, stops the release. Re-run this workflow + # once CI is green. - name: Require a successful ci.yml run env: GH_TOKEN: ${{ github.token }} diff --git a/AGENTS.md b/AGENTS.md index a33fe31..d652974 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,14 +80,14 @@ Run everything from the repo root, with a .NET 10 SDK (`global.json`). Docker is available and a missing emulator fails the run (`WFP_REQUIRE_DOCKER=1`), and the build and tests on Windows. Its Native AOT job publishes `tests/WriteableFileProviders.Aot` with native AOT, which fails on any trim or AOT warning in the packages or the SDKs they call (but one, its README says which), and runs it on the local disk, - the gateway, Azurite, MinIO and the Graph fake. Its Physical jobs build and run Physical's tests on macOS and on Linux - on Arm64. + the gateway, Azurite, MinIO and the Graph fake. Its Physical jobs build and run Physical's tests on macOS and on + Linux on Arm64. - **Release:** a maintainer tags `v` on `main`. `release.yml` builds, runs the tests with coverage and the coverage gate, packs, checks that every package has that version and depends on our other packages within its major - (`eng/package-check.cs`), and requires that `ci.yml` concluded success on the tagged commit (its Windows and Physical - jobs included); - then, once a maintainer approves the `nuget` environment, it pushes the packages to nuget.org through trusted - publishing and creates the GitHub release, with the version's section of `CHANGELOG.md` as its notes. + (`eng/package-check.cs`), and requires that `ci.yml` concluded success on the tagged commit (its Windows, Native AOT + and Physical jobs included); then, once a maintainer approves the `nuget` environment, it pushes the packages to + nuget.org through trusted publishing and creates the GitHub release, with the version's section of `CHANGELOG.md` as + its notes. [docs/releasing.md](docs/releasing.md) has the steps. ### Tests diff --git a/REVIEW.md b/REVIEW.md index 7142277..f0f6388 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -9,12 +9,11 @@ acceptable. 1. **Build and tests.** Is CI green on the PR's head commit (`AGENTS.md`, Build and test)? `ci.yml` builds the solution, runs every test outside the `Live` category on Linux, where Docker runs the emulator suites and a missing emulator fails the run, and on Windows, then runs the coverage gate and packs, its Native AOT job publishes - `tests/WriteableFileProviders.Aot` with native AOT and runs it, and its Physical jobs run Physical's tests on macOS and - on Linux on Arm64; `codeql.yml` scans the code and the workflows. CI - never runs the live suites against real accounts: for a change to a provider, does the PR say whether its live - suite ran, and if it did not, why? For a PR from outside the maintainers, the answer comes from CI, never - from running its code on a maintainer's machine, so a live suite it needs runs only once the change is on `main`, - through `live.yml`, and the PR says so. + `tests/WriteableFileProviders.Aot` with native AOT and runs it, and its Physical jobs run Physical's tests on macOS + and on Linux on Arm64; `codeql.yml` scans the code and the workflows. CI never runs the live suites against real + accounts: for a change to a provider, does the PR say whether its live suite ran, and if it did not, why? For a PR + from outside the maintainers, the answer comes from CI, never from running its code on a maintainer's machine, so a + live suite it needs runs only once the change is on `main`, through `live.yml`, and the PR says so. 2. **One-way doors.** Does the change leave the one-way doors in `AGENTS.md` alone, or does its PR name the one it changes and link the issue where the maintainers agreed? 3. **Every provider.** Is a change to Common's contract or to shared behaviour made in all seven providers, tested on diff --git a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs index 6d2374b..58323bd 100644 --- a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs +++ b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs @@ -589,13 +589,13 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(file + Path.DirectorySeparatorChar)); Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(file, "."))); Assert.AreEqual(WriteablePhysicalFileProvider.PhysicalPathOf(folder), WriteablePhysicalFileProvider.PhysicalPathOf(folder + Path.DirectorySeparatorChar)); - Assert.AreEqual(Path.Combine(Path.GetDirectoryName(physicalFile)!, "missing.txt"), WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "missing.txt"))); + // The folder as the kernel finds it: on macOS the temp folder is under /var, a link to /private/var. + var physicalFolder = Path.GetDirectoryName(physicalFile)!; + Assert.AreEqual(Path.Combine(physicalFolder, "missing.txt"), WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "missing.txt"))); - // Chains of links: one through exactly the limit resolves, one link more does not (32 on macOS, 40 elsewhere). They - // are made in the folder as the kernel finds it, so that no link of its own path counts: on macOS the temp folder - // is under /var, a link to /private/var. + // Chains of links: one through exactly the limit resolves, one link more does not (32 on macOS, 40 elsewhere), + // for the provider and for the kernel. They are made in that folder, so that no link of its own path counts. Assert.AreEqual(OperatingSystem.IsMacOS() ? 32 : 40, WriteablePhysicalFileProvider.MaxLinks); - var physicalFolder = Path.GetDirectoryName(physicalFile)!; try { Chain("a", WriteablePhysicalFileProvider.MaxLinks); @@ -607,6 +607,11 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Chain("b", WriteablePhysicalFileProvider.MaxLinks + 1); Assert.AreEqual(physicalFile, WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "a0"))); Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "b0"))); + if (!OperatingSystem.IsWindows()) + { + Assert.AreEqual("x", File.ReadAllText(Path.Combine(physicalFolder, "a0")), "The kernel follows the limit's links"); + Assert.ThrowsExactly(() => File.ReadAllText(Path.Combine(physicalFolder, "b0")), "and refuses one more (ELOOP)"); + } // prefix0 -> prefix1 -> ... -> prefix(count - 1) -> file.txt: a path through count links. void Chain(string prefix, int count) From 88d2d5cb7ecb95aac446c50aac637d58cc3fe1b8 Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 11:25:27 +0200 Subject: [PATCH 4/7] Wrap the link-chain test's new lines Refs #73 --- .../PhysicalProviderTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs index 58323bd..da2e297 100644 --- a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs +++ b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs @@ -591,7 +591,8 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.AreEqual(WriteablePhysicalFileProvider.PhysicalPathOf(folder), WriteablePhysicalFileProvider.PhysicalPathOf(folder + Path.DirectorySeparatorChar)); // The folder as the kernel finds it: on macOS the temp folder is under /var, a link to /private/var. var physicalFolder = Path.GetDirectoryName(physicalFile)!; - Assert.AreEqual(Path.Combine(physicalFolder, "missing.txt"), WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "missing.txt"))); + Assert.AreEqual(Path.Combine(physicalFolder, "missing.txt"), + WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(folder, "missing.txt"))); // Chains of links: one through exactly the limit resolves, one link more does not (32 on macOS, 40 elsewhere), // for the provider and for the kernel. They are made in that folder, so that no link of its own path counts. @@ -609,8 +610,8 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "b0"))); if (!OperatingSystem.IsWindows()) { - Assert.AreEqual("x", File.ReadAllText(Path.Combine(physicalFolder, "a0")), "The kernel follows the limit's links"); - Assert.ThrowsExactly(() => File.ReadAllText(Path.Combine(physicalFolder, "b0")), "and refuses one more (ELOOP)"); + Assert.AreEqual("x", File.ReadAllText(Path.Combine(physicalFolder, "a0")), "The kernel follows the limit's links,"); + Assert.ThrowsExactly(() => File.ReadAllText(Path.Combine(physicalFolder, "b0")), "and refuses one more"); } // prefix0 -> prefix1 -> ... -> prefix(count - 1) -> file.txt: a path through count links. From 941014fd430aa3af2db7abfcd81c324b9a61d6f6 Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 11:25:43 +0200 Subject: [PATCH 5/7] Name the paths the kernel check reads Refs #73 --- .../PhysicalProviderTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs index da2e297..8760ce1 100644 --- a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs +++ b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs @@ -610,8 +610,10 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() Assert.IsNull(WriteablePhysicalFileProvider.PhysicalPathOf(Path.Combine(physicalFolder, "b0"))); if (!OperatingSystem.IsWindows()) { - Assert.AreEqual("x", File.ReadAllText(Path.Combine(physicalFolder, "a0")), "The kernel follows the limit's links,"); - Assert.ThrowsExactly(() => File.ReadAllText(Path.Combine(physicalFolder, "b0")), "and refuses one more"); + var limit = Path.Combine(physicalFolder, "a0"); + var past = Path.Combine(physicalFolder, "b0"); + Assert.AreEqual("x", File.ReadAllText(limit), "The kernel follows the limit's links,"); + Assert.ThrowsExactly(() => File.ReadAllText(past), "and refuses one more"); } // prefix0 -> prefix1 -> ... -> prefix(count - 1) -> file.txt: a path through count links. From de8147338257c41fbf36ce1b8573d7144d54e209 Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 11:25:58 +0200 Subject: [PATCH 6/7] Wrap the chain's link line Refs #73 --- .../PhysicalProviderTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs index 8760ce1..35bbad8 100644 --- a/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs +++ b/tests/WriteableFileProviders.Physical.Tests/PhysicalProviderTests.cs @@ -620,7 +620,8 @@ public void PhysicalPathOf_StopsWhereTheKernelDoes() void Chain(string prefix, int count) { for (var i = 0; i < count; i++) - File.CreateSymbolicLink(Path.Combine(physicalFolder, $"{prefix}{i}"), i == count - 1 ? "file.txt" : $"{prefix}{i + 1}"); + File.CreateSymbolicLink(Path.Combine(physicalFolder, $"{prefix}{i}"), + i == count - 1 ? "file.txt" : $"{prefix}{i + 1}"); } } From 67020c17fb571e559dbd93f0d445ee2551a6d33a Mon Sep 17 00:00:00 2001 From: Osvay Soma Date: Tue, 13 Oct 2026 11:37:05 +0200 Subject: [PATCH 7/7] Log step 2 in the 2027.1.1 plan Refs #73 --- docs/plans/2026-10-13-2027-1-1.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/plans/2026-10-13-2027-1-1.md b/docs/plans/2026-10-13-2027-1-1.md index 949c6a1..0cf9ceb 100644 --- a/docs/plans/2026-10-13-2027-1-1.md +++ b/docs/plans/2026-10-13-2027-1-1.md @@ -459,4 +459,11 @@ expectation (the winner is done before the loser retries); the locked restore of is read past again; G3, the live fixture says the race test can fail by design without hard links. Round 3: G1 to G3 verified; H1, the interface's exception doc now says what errors.md says; H2, a test pins the Windows read-past with an empty winner and the refusal elsewhere; H3 and H4, a reflow and one reason for the corner. Round 4: H1 to H4 - verified, clean; three wording nits applied. + verified, clean; three wording nits applied. Merged as #74. +- **2026-10-13, step 2 (#73):** the two Physical jobs, `Physical (macOS)` and `Physical (Linux, Arm64)`. Their first runs: + Arm64 passed as it was; macOS failed one test, a link chain made under `/var` (a link to `/private/var`), so it passed + one link more than counted. The chains are now made in the folder's physical path, and the test also reads through + them, so Darwin's limit (32) and Linux's (40) are pinned on the kernel. The docs name the jobs, and the Native AOT job + that the release docs had left out. Code review: round 1, no blocker or major, five fixes (two on the maintainer's + yes); round 2 clean. On the maintainer's choice, both jobs become required checks on `main` after the merge (P8's + ruleset question).