mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-17 05:11:13 +08:00
master
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8e96408b2a |
Fuzz oracles: confirm net48 allocation findings by re-running the target (#4818)
## Problem `FuzzCrashAssets(AflfuzzBinaryDecoder)` failed intermittently on net48 (run 38897795750, test-windows-net48 1/16, on #4815, which changes only `perf/`): > 10562496 bytes allocated for 42 input bytes, budget 8391296 bytes On .NET Framework, `FuzzOracles` measures allocations with `AppDomain.MonitoringTotalAllocatedMemorySize`. That counter covers the whole AppDomain, so it also counts other threads in the test host. Measured on net48 Release for `crash-0bdcbfafc13981ae6f665ad52cabc1cbe21b0238`: | Scenario | Bytes | |---|---| | First decode in the process (one-time XML setup) | 1,083,232 | | Later decodes, same thread or a fresh thread | 16,440 | | Same decode with one concurrent thread allocating | 10.7 to 23.8 MB (over budget) | The decoder does not have a per-thread cost, and #4812 did not regress it. The CI TRX shows that tests ran one at a time. The failure came about 2 s into the run, right after a burst of about 100 fast test cases, which fits vstest serializing results on a background thread. ## Fix `FuzzOracles.RunTarget` re-runs a target when the allocation finding comes from a counter that is not per thread (`AllocationConfirmationRuns = 2`). It reports the finding only if every run confirms it. A real over-budget decode allocates the same amount every time, while a burst on another thread rarely lines up with all three runs. This is the same approach the time oracle already uses (`TimeConfirmationRuns`). Runtimes with a per-thread counter (net8+) behave as before. `FuzzMethods` playback goes through `RunTarget` too, so it also gets the confirmation runs. ## Tests - New tests in `FuzzOraclesTests`: - A finding that every run confirms is still reported, after 3 runs on net48 and 1 run on net8+. - A finding inflated by another thread is ignored (net48 only). This test fails when the confirmation runs are set to 0. - net48 Release: Opc.Ua.Fuzzing.Tests 156/156, Opc.Ua.Encoders.Fuzz.Tests 8130/8130. - net10.0: FuzzOraclesTests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
52f8520baa |
Detect resource and limit abuse in the encoder/decoder fuzz harness (#4666)
## Why The encoder/decoder fuzz targets only flagged crashes, unhandled exceptions and encoding-fidelity mismatches. They could not observe a decode that stays within the exception contract but consumes far more memory, CPU or stack than its input justifies, or that materialises values larger than the context's encoding limits. None of the resource-related findings of the codec audit would have been caught. ## What Adds four shared oracles to the fuzz harness (`fuzzing/Common/Fuzz/FuzzOracles*.cs`), applied by every host (libFuzzer, AFL, `--replay`, Tools playback and the NUnit replay): - **Allocation** — wraps the Binary/JSON/XML decode cores, the built-in-type targets, the PubSub UADP/JSON decoders and the UADP reassembler; fails when a decode allocates more than `64x` its input plus `8 MB`, whether it returns or throws. Uses the per-thread allocation counter (the AppDomain counter on .NET Framework). - **Time** — a per-input budget of `1 s + 100 ms/KB`, re-run twice and reported only if the fastest run is still over, so JIT/GC/CI noise does not trip it (`OPCUA_FUZZ_TIME_BUDGET_SCALE`, `0` disables). - **Stack** — every input runs on a worker thread with a `256 KB` stack, so recursion that comes close to the limit of a normal thread is caught in-process as a crash rather than near 200 levels. `StackTestcases` inputs replay through every target in a child process (`--fuzz-replay-all`), since such a crash cannot be caught. - **Decoded limits** — walks a decoded value for strings, identifiers, ByteStrings and arrays beyond the context's `MaxStringLength`/`MaxByteStringLength`/`MaxArrayLength`; matrices are additionally boxed the legacy way so a value no consumer can materialise is reported. The fuzz context uses `4096/8192/4096` so small inputs reach the limits. Supporting changes: - `ArrayOf<T>`/`MatrixOf<T>` are not `IEnumerable`, so the limit walker unpacks them via `ToArray`/`ToArrayOf` (otherwise it silently traversed nothing). - `Decimal` is registered in the fuzz context so the targets exercise `Decimal.Decode`. - **B1-2 bug fix**: `BinaryDecoder.ReadExtensionObject` dereferenced a null element for an XML body that is not an element; now rejected with `BadDecodingError`. Found by replaying the nightly crash corpus under the new exception contract; 5 corpus inputs are added as gating reproducers. - Reproducers for B1-1, T1-3, X1-1 and U1-3, built in process by documented builders (`ResourceReproducerTests`, `PubSubReproducerTests`) and checked in to the corpus, where every target replays them under the oracles. Each was verified by reverting the matching fix locally and confirming the guard fires (e.g. B1-1 then allocates ~21 MB from 481 input bytes). - `Fuzzing.md` / `CrashCorpus.md` document the oracles and the private-input log policy. ## Testing Green on net10.0 and net48: | Suite | net10.0 | net48 | |---|---|---| | `Opc.Ua.Encoders.Fuzz.Tests` | 8130 | 8130 | | `Opc.Ua.PubSub.Fuzz.Tests` | 149 | n/a (modern-only) | | `Opc.Ua.Fuzzing.Tests` | 154 | 154 | | `Opc.Ua.Types.Tests` (encoders) | 3698 | builds | Also verified: `Network`/`Certificates` fuzz test suites, all fuzz hosts and Tools projects build, the fuzz manifest validator passes, `dotnet format whitespace` is clean, and the full ~22k-input nightly crash corpus replays with no oracle false positives. Stacked on `romanett/encoder-decoder-spec-security-83c9f5`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d623007a3f |
Remove the Azure Pipelines CI and publishing definitions from master (#4575)
# Description Removes the Azure Pipelines CI and package-publishing definitions from `master`. GitHub Actions already owns all CI and package publishing for `master`, but pushes to `master` still queued the Azure definitions. They duplicated the Actions work and posted failing checks, for example [`Nuget Preview (Push Nuget Preview Pack Nugets Debug)`](https://github.com/OPCFoundation/UA-.NETStandard/runs/109065914524) on `19dc504c5`. ## Removed - `azure-pipelines.yml`, `azure-pipelines-preview.yml` - `.azurepipelines/` templates: `ci.yml`, `coverage.yml`, `preview.yml`, `sample-ha-longhaul.yml`, `sln.yml`, `test.yml`, `test-aot.yml` - Helpers only those templates used: `get-matrix.ps1`, `get-root.ps1`, `evaluate-test-results.ps1` - `TrxGateScriptTests`: it tested only `evaluate-test-results.ps1`. The Actions equivalent, `.github/scripts/get-test-verdict.ps1`, keeps its own `CiTestVerdictTests`. ## Kept **OneFuzz:** `.azurepipelines/onefuzz.yml` stays. Submitting to the internal OneFuzz service needs the Azure DevOps-only `onefuzz-task`, which has no GitHub Actions equivalent. The pipeline has no push, PR or schedule trigger, so it never runs on `master` pushes. The public drop build keeps running in the GitHub `fuzz-public-drop` job. **Scripts GitHub Actions still invokes:** these stay in `.azurepipelines/`, so every workflow path and `release.yml`'s candidate-bound checkout keep working. Only `generate-slnx.ps1` changes, and only its comment. | Script | Used by | | --- | --- | | `set-version.ps1` | buildandtest, nightly, nuget-publish, docker-image, stress / stability / long-haul, `run-dotnet-tests` action | | `generate-slnx.ps1`, `signlist{Debug,Release}.txt`, `validate-source-generator-packages.ps1` (+ `expected-packages.txt`) | nuget-publish | | `package-version-policy.ps1`, `validate-nuget-package-set.ps1`, `validate-preview-package-ordering.ps1`, `assert-published-packages-match.ps1` | nuget-publish, release | | `check-coverage.ps1` | buildandtest, nightly | | `validate-migration-plugin.ps1` | buildandtest | I didn't rename the directory on purpose. `release.yml` checks out the candidate commit and runs `./.azurepipelines/package-version-policy.ps1` from it, so moving the scripts would break promotion of any candidate built before the move. ## Docs and comments - `UA.slnx` drops the deleted files. - `codecov.yml`, `coverage-thresholds.json`, `targets.props`, the Redundancy sample test comments, the `.github` scripts and workflows, `CONTRIBUTING.md`, `docs/` and `fuzzing/` no longer describe Azure as running on `master` (`/azp run`, the `azure` Codecov flag, "the Azure gate", the Azure long-haul and AoT legs). - In the Developer Guide, the "Triggering a pipeline run" and "Migration status" sections are replaced by "Pull requests from outside contributors" and "Azure Pipelines on other branches". - The new section names `onefuzz.yml` as the one remaining Azure definition. - It keeps the warning that the `Release` ruleset's Azure required check must be lifted before cutting a new `release/2.<minor>` branch. - `master` is merged in, including the #4555 docs rewrite. Its new `docs/RedundancySampleTests.md` needed the Azure references removed again. `docs/README.md` already had none. ## Reviewer notes - **Azure DevOps side (admin action, not in this PR):** the definitions are configured in the Azure DevOps portal. With the YAML gone, `master` pushes give the CI and preview definitions nothing to run. Disabling definition 14's push trigger/schedule, definition 16's build-completion trigger, and the stale definition 13 stops any residual "file not found" runs. Leave the OneFuzz definition enabled. - `master378`, the 1.x `release/*` lines and the frozen `release/2.0.0` keep their own `azure-pipelines.yml`. This PR only touches `master`. ## Related Issues - Follow-up to #4513 (GitHub Actions for CI and nightly). ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [ ] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. This PR removes a test and adds none. Locally, at `9b00da817`, the full `Opc.Ua.Tools.Tests` suite passed on net10.0: 884 passed, 1 skipped. The later commits (OneFuzz restore, merge with `master`) touch only YAML and docs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e0c56a7c00 |
Replay the fuzz crash corpus from the fuzz-corpus orphan branch (#4539)
# Description Since the move to GitHub Actions, CI replays only the checked-in fuzz corpus. The full crash corpus used to be the Azure DevOps secure file `FuzzingArtifacts.zip`, which `.azurepipelines/test.yml` overlaid onto `Opc.Ua.Encoders.Fuzz.Tests`. The nightly `private-corpus` job that was meant to replace it depended on an Azure storage account, federated credentials and a protected environment. None of these were ever provisioned, so the job never ran. **The corpus now lives on the orphan branch [`fuzz-corpus`](https://github.com/OPCFoundation/UA-.NETStandard/tree/fuzz-corpus)** (commit `843ba944a7b144a7ae6429151846d9e5ab28d29f`, already pushed): - It contains 22,267 `crash-*`, 25 `slow-unit-*` and 1 `timeout-*` input under `Opc.Ua.Encoders.Fuzz.Tests/Assets/`. - The files are byte-identical to the secure file (SHA-256 `85198e46…fe1d61b`), recovered from the `drop` artifact of Azure build 18397. - The branch shares no history with `master`, so clones of `master` don't get 22k extra files in their checkout. **Why the corpus is no longer treated as private:** - The inputs date from April–June 2024. - The `drop` artifacts could be downloaded anonymously. - Every master build printed up to about 7k base64 reproducers into anonymously readable Azure logs. - Current `master` replays every input without a test failure. New, unfixed crashes still go through [SECURITY.md](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/SECURITY.md) and are added to the branch only after the fix ships. ## Changes - **`nightly.yml`:** `private-corpus` is replaced by `crash-corpus`. The new job: - checks out the commit pinned in `FUZZ_CORPUS_COMMIT` (no secrets, environment or OIDC); - verifies the SHA and copies each top-level folder over `fuzzing/<folder>`; - runs `Opc.Ua.Encoders.Fuzz.Tests` on the 12 non-macOS profiles through the shared `run-dotnet-tests` action and uploads `crash-corpus-results-*`. The `run_private_corpus` input and the INCOMPLETE label are removed. The job runs on every scheduled and manual run, and the nightly summary fails if it was skipped. - **`fuzzing/CrashCorpus.md` (new):** the Azure history, branch layout, how CI replays the corpus, local replay, and how to add inputs and move the pin. `Fuzzing.md` links to it. - **`docs/DeveloperGuide.md`:** the private-corpus section and its secrets table are replaced, and the migration to-do list is updated. - **`nightly.yml` AOT job (fix for a failure found by the first nightly run):** the Linux and macOS legs looked up the published binary by `BaseName`/`Extension`. A NativeAOT executable has no extension on those platforms, so PowerShell splits `Opc.Ua.Aot.Tests` into `Opc.Ua.Aot` + `.Tests`, and every leg reported *No NativeAOT executable was produced* even though publish succeeded ([run 36017168717](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/36017168717), `aot-ubuntu-latest`). The lookup now matches the exact file name. A failure now reports the publish errors, a missing publish directory, or which files were produced instead. - **`Opc.Ua.Redundancy.Samples.Tests` / `Opc.Ua.WotCon.Samples.Tests` (fix for failures found by the first nightly run):** the test runner's framework guard rejected both projects on the nightly net8.0, net9.0, net472 and netstandard2.1 profiles. Redundancy built a net48 placeholder for every target except net10.0, and WotCon was pinned to net10.0. Both now build the requested target on net8.0/9.0/10.0 and use `RestrictForLegacyTfm` to become explicit not-applicable shells on net48, net472, netstandard2.0 and netstandard2.1. The redundancy tests no longer hard-code `bin/<cfg>/net10.0` when locating the sample binaries, and `SampleAppProcess` falls back to an `object` lock below net9.0. The net48 placeholder remains for Azure's plain `dotnet test -f net48` leg. Checked locally by building Redundancy for `CustomTestTarget=net9.0` and WotCon for `netstandard2.1`. - **`DefaultServerRedundancyHandler` (fix for a failure found by the first nightly run):** `ResolveServerAsync` gave the resolver a token linked to the caller's, but waited on the caller's token directly. On cancellation the wait could finish, and its `using` could dispose the linked source, before the linked source's own callback had run. That unhooks it, so the in-flight peer lookup never saw the cancellation (`PeerDiscoveryPropagatesCallerCancellationAsync`, netstandard2.0 build on net48). The wait now uses the linked token. A timeout is still logged as a discovery failure, as before. A companion test fix makes `PeerTimeoutPreservesOtherEndpointsAndCoalescesPendingDiscoveryAsync(False)` deterministic. The timed-out lookup drains on the thread pool, so the test's retry could join it and then have its own time limit expire before it started a fresh lookup. The test now waits for the second resolver call before advancing the fake clock. - **`Session` keep-alive logging (found while investigating the net48 nightly failures):** `OnKeepAliveError` and the keep-alive read failure log used `Endpoint?.EndpointUrl`. `ClientBase.Endpoint` dereferences the transport channel, so once the channel was detached it threw a `NullReferenceException` on every keep-alive tick, and `BadNoCommunication` never reached the `KeepAlive` subscribers. Both now log the configured endpoint's URL. Three new `SessionKeepAliveTests` cover a detached channel. - **`ClientTestFramework` shared session (fix for 67 `Opc.Ua.Sessions.Tests` failures on the net48 nightly):** the fixture-wide shared session used the 10 s default session timeout. A test-host stall of about 11–18 s while other fixtures restarted their servers let the server expire it, and every later test in the `ClientTest("https")` and `ClientTest("opc.https")` fixtures failed with `BadSessionIdInvalid`. The shared session now requests `SharedSessionTimeout` = 120 s; per-test sessions keep 10 s. - **`run-dotnet-tests.ps1`:** `-QuietOutput` help text only; it no longer names the removed job. The Azure secure file stays in place, because `master378` and the 1.x release pipelines keep their own `test.yml` and still use it. ## Verification - `nightly.yml` parses as valid YAML, and no job, output or summary field still refers to the removed job. - I ran the job's overlay script against a clone of `fuzz-corpus` at the pinned commit. It overlaid all 22,293 files, left the tracked `Assets/Repo` untouched, removed the checkout folder and wrote the summary line. - **Not yet run:** the workflow itself. It is `schedule`/`workflow_dispatch` only, so it needs a manual `nightly.yml` run on this branch. On master the corpus has so far only been replayed on Windows (net48/net10.0 in Azure), so the Linux legs are new ground. ## Follow-ups (admin) - Add a ruleset for `fuzz-corpus`: require pull requests and block force pushes and deletion, so the pinned commit stays reachable. - The `fuzz-private-corpus` environment, if it was ever created, can be deleted. ## Related Issues - No issue exists yet; this is part of the Azure DevOps → GitHub Actions migration (#4513, #4524). ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [ ] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f5ad124891 |
Additional fuzzing plus fixes for every defect the campaigns found (#4459)
# Description
Brings the stack's fuzz testing to parity with the fork
(`aio-opcua-sdk`), then fixes every defect the resulting campaigns
exposed.
The fork's fuzzing was used as the behavioural baseline: all **28
fork-local callbacks** and **13 OneFuzz registrations** are now
accounted for by an explicit mapping, not just by a larger target count.
The work extends the existing SharpFuzz/NUnit infrastructure rather than
introducing another framework.
### Coverage
- **84 continuous libFuzzer targets** and **81 AFL adapters** across
Encoders, Certificates, Network and PubSub.
- **PubSub area integrated** (Corpus/Tools/Tests siblings, solution
entries) — it previously had host source only and was absent from the
solution and replay infrastructure.
- Segmented binary adapters, binary-to-JSON and XML-to-JSON conversions,
standalone certificate loading, both chain-parser variants, CRL encoding
and canonical TBS round-trips.
- The 12 original fork message inputs are retained byte-identically
alongside richer deterministic generators; the three historical
crash/slow/timeout reproducers are unchanged (verified identical by
SHA-256).
### Production fixes found by fuzzing
These are real robustness and data-integrity bugs, each with a retained
regression input:
- `UadpBinaryReader.Advance` accepted attacker-controlled lengths on the
receive path. Declared payload sizes are now authoritative bounded
sections, with negative-count rejection and overflow-safe reassembly
arithmetic.
- PubSub JSON decoding leaked `InvalidOperationException` for invalid
UTF-8.
- `JsonEncoder.WriteExtensionObject` leaked
`System.Text.Json.JsonReaderException`.
- `JsonEncoder` silently emitted a lossy `{}` for an `ExtensionObject`
whose TypeId could not be represented in JSON.
- `JsonDecoder` wrapped a bodyless `ExtensionObject` envelope as a raw
JSON body instead of preserving `ExtensionObjectEncoding.None`.
- `Node.Encode` lost the null/empty collection distinction.
- Binary/XML Variant matrix dimensions are prevalidated rather than
relying on a caught `ArgumentException`.
- `X509CRL.ReadTime` pre-validates strict RFC 5280 whole-second Zulu
time forms.
- `Utils.ParseCertificateChainBlob` disposes correctly on partial-chain
failure.
- **`nodeid.dict` was unloadable by libFuzzer** (empty token, unescaped
quote). libFuzzer rejects the whole file on one bad line, so four parser
targets silently executed **zero** inputs. Fixed and now guarded by
dictionary validation.
### Infrastructure
- `fuzzing/fuzz-targets.json` manifest recording adapters, corpora,
dictionaries, oracles and fork-behaviour mappings, with
`validate-fuzz-manifest.ps1` and **27 negative controls**.
- Strict host/tools discovery and replay: `--list`, `--replay`, non-zero
exits on unknown targets, missing or empty corpora, and failed callbacks
(playback previously logged failures and returned success).
- A real process watchdog for the timeout/slow regressions — NUnit
`CancelAfter` cannot interrupt a synchronous fuzz callback.
- Runner scripts made non-destructive (unique work directories, no
generic `bin`/findings deletion) and corrected to the native driver's
single-`--target_arg` apphost contract.
- Opt-in `OneFuzz=true` class-library publication producing a
relocatable, **uninstrumented** drop, validated by exact callback
discovery, dependency closure, corpora and dictionaries.
- CI: fuzz replay matrix plus a public drop gate, triggered by corpus-,
dictionary- and script-only changes.
### Verification
| Gate | Result |
|---|---|
| Bounded campaigns, all 84 targets (1,000 inputs, seed 1) | 84/84,
84,000 executions, zero findings |
| OneFuzz drop | 84/84 published callbacks replayed, zero skips |
| Fuzz replay net10.0 / net8.0 / net9.0 | 9,470 passed each, 0 failed |
| Fuzz replay net48 | 8,996 passed, 0 failed (Network/PubSub are
modern-only) |
| `Opc.Ua.Types.Tests` / `Opc.Ua.Core.Tests` | 9,800 and 4,442 passed, 0
failed |
| Retained regression assets | 132 `crash-*` inputs replayed by every
target each run |
### Known gaps (deliberately not closed)
- **Fidelity is not enforced against the pipeline crash corpus.** The
differential binary-to-JSON targets assert both robustness (no unhandled
exception, expected of arbitrary bytes) and fidelity (a decoded value
re-encodes equivalently, a property of well formed values). The pipeline
supplies 22,293 blobs collected for the targets that existed before this
PR, none of which asserted fidelity; 522 of them violate it, and that
count did not move across two rounds of genuine fixes because each fix
only lets the mutator reach the next representability gap. Rather than
weaken the assertion, the two contracts are separated: fidelity failures
raise `EncodingFidelityException`; curated assets under `Assets/Repo`,
the curated `Testcases` corpus and every continuous libFuzzer campaign
stay strictly enforced, while externally supplied assets are gated on
robustness only and their fidelity findings are reported with counts and
bounded base64 reproducers.
- **Internal MSFT OneFuzz canary has not been run.** It needs an
owner-supplied ownership profile, pipeline authorisation and corpus
snapshot; none were available. The drop builds and validates locally and
records `serviceCanaryPerformed: false`. A build or upload is not a
successful worker run.
- **Legacy JSON wire forms are a partial mapping.** The fork accepts its
four original `TypeId`/`Body` messages; this stack rejects that retired
envelope (measured JSON-only control: fork `JsonEncoder` 630/1356 lines
vs 0/1007 here). Recorded as an explicit unresolved mapping rather than
an exemption. `LegacyNonReversible` is a RawData-derived profile and is
not counted as independent coverage.
## Related Issues
_No tracking issue was supplied for this work; happy to open one and
link it if maintainers prefer._
- Fixes #github-issue-number, ...
## Checklist
- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
> Note on the checklist: the fuzz replay matrix, `Opc.Ua.Types.Tests`
and `Opc.Ua.Core.Tests` were run locally on **net48** and **net10.0**
(plus net8.0/net9.0) and all passed. CI and CodeQL results are not yet
available for this branch, so those boxes are left unchecked. The CLA
box is for the author to confirm.
|
||
|
|
ea0f266b66 |
Move build and test to the Azure DevOps managed pool and replace Codecov with an in-pipeline coverage gate (#4122)
# Description Moves the all-target-framework build, the cross-platform test matrix and the Native AoT run from GitHub Actions onto the Azure DevOps `netstandard` Managed DevOps Pool, and replaces Codecov with a coverage gate that runs inside the pipeline. ## 1. CI backend switch One conceptual switch decides who owns that work. It is checked into source in **two places that must be flipped together**: | File | Setting | Default | | --- | --- | --- | | `azure-pipelines.yml` | `parameters.ciBuildBackend` | `ado` | | `.github/workflows/buildandtest.yml` | `env.CI_BUILD_BACKEND` | `ado` | Setting both to `actions` restores the previous behaviour exactly. Azure Pipelines gains what it was missing to take the work over: - A **Linux all-TFM `sln.yml` leg**, replacing `build-all-tfm-linux`. `sln.yml` gets a `configurations` parameter so this leg builds `Release` only instead of doubling the managed-pool job count. - A **Linux agent in the `net10.0` `testfastpr` matrix**, replacing the ubuntu `build-and-test` job. - **Native AoT and Code Coverage now gate pull requests** instead of running only on Schedule/Manual. The coverage stage drops its `testdebug` dependency, which is Schedule/Manual-only and would otherwise have skipped coverage on every PR. - On a PR, Windows builds the .NET Framework TFMs and Linux the modern ones — mirroring the split the GitHub Actions jobs used — so all seven TFMs stay covered without fanning every TFM across both pools. Two things are deliberately **not** covered by the switch: - **macOS** stays on GitHub-hosted runners, because Managed DevOps Pools provide no macOS image. - **`master378` and `develop/*`** keep running the GitHub Actions jobs, since Azure Pipelines only builds `master`/`main` from this file. GitHub Actions does not expose the `env` context to job-level `if:` conditions, so the switch is resolved once in the existing `discover` job and re-exported as job outputs. A new **`build-and-test summary`** job gives branch protection one stable check to require, instead of the generated matrix job names. It treats an intentionally skipped job as success but still fails on a real failure or cancellation. The `paths:` allow-list was moved off the `pull_request` trigger and into `discover`. A workflow filtered out by `paths` never reports its checks, and a required check that never reports leaves a PR permanently *"Expected — waiting for status to be reported"*. That would have made the routine "ratchet the coverage floor" PR (which touches only `coverage-thresholds.json` and docs) unmergeable. ## 2. Coverage gate replacing Codecov Codecov is removed entirely — `codecov.yml`, both uploads, the badge, and the token usage. `.azurepipelines/check-coverage.ps1` runs at the end of the `Code Coverage` stage against the Cobertura report ReportGenerator already merges, driven by `coverage-thresholds.json`: | Check | Behaviour | | --- | --- | | Project floor | **Blocking** — total line and branch rates against an absolute floor. | | Patch coverage | **Blocking on PRs** — changed lines must reach `patch.target`, tolerating `patch.threshold` percentage points (the same semantics `codecov/patch` had). | | Baseline delta | **Advisory** — reports the change against the recorded baseline, never fails. | The project rate is recomputed from the per-line data rather than read off the Cobertura root element, so the `ignore` globs carried over from `codecov.yml` apply to the project floor as well as to the changed-lines gate — otherwise samples and generated code would count towards one gate but not the other. `testcc.yml` gains an explicit `checkout: self` with `fetchDepth: 0`, because the default shallow clone cannot diff against the base branch. ## Follow-up actions for maintainers Two items cannot be automated from source: 1. **Enable the `/azp run` gate.** In the Azure DevOps portal: pipeline → **More actions** → **Triggers** → **Pull request validation** → **Require a team member's comment before building a pull request**, scoped to *Only on pull requests from non-team members*. This makes Copilot-authored and outside-contributor PRs wait for a maintainer's `/azp run`, mirroring the approval GitHub Actions already requires. The documentation added here describes the workflow and the private-org-membership gotcha; **the setting itself still has to be switched on.** 2. **Update branch protection and secrets.** Drop the `codecov/project` and `codecov/patch` required checks, require `build-and-test summary` and the ADO coverage check instead, and remove `CODECOV_TOKEN` from the GitHub repository secrets and the ADO variable group. Also note the thresholds in `coverage-thresholds.json` are seeded from the last Codecov reading (73.61 %) with headroom, because the in-pipeline number had not been observed yet. The first full `Code Coverage` run on master should confirm it and the floors should be tightened then — the gate prints the exact figure it computes. ## Related Issues _No tracking issue; this is CI infrastructure work requested directly._ ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - No product code changed, so there are no unit tests to add. The new logic was verified with purpose-built harnesses instead: - All 8 branch/event/backend combinations of the `discover` switch, confirming `master378` and `develop/*` are untouched and that `actions` restores the old behaviour. - 12 scenarios for `check-coverage.ps1`: the project floor, the patch gate, ignore globs applying to *both* gates, three path-normalization shapes, and the advisory delta. - 14 path-relevance cases confirming the in-job filter matches the retired `paths:` allow-list exactly. - End-to-end against a **real** ReportGenerator report: correctly resolved `src/Opc.Ua.PubSub/DependencyInjection/PubSubBuilder.cs`, found exactly the four modified tracked lines and reported 50 % patch coverage (2 covered, 2 uncovered). - [x] I have added all necessary documentation. - New **Continuous integration** section in `docs/DeveloperGuide.md`, a CI note in `CONTRIBUTING.md`, and links from `docs/README.md`. Also corrected the CI matrix description, which claimed the PR gate ran net48 + **net8.0** — it runs net48 + **net10.0**. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - `dotnet build src/Opc.Ua.Server` → 0 warnings, 0 errors. No product code changed. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - Not applicable — no product code changed. `Opc.Ua.Server.Tests` was run on net10.0 during development (3480 passed; the single `ServerFluentApiHostingTests` failure is pre-existing and reproduces on master). - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. - A review pass over this branch flagged the `paths:`-filter deadlock described above; it is fixed in `1926e240e`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 181ba7ea-c72f-42d7-a737-00a3faadaa17 Copilot-Session: 26aa55a2-5731-4a4c-b61c-df2f7a785eca |
||
|
|
8538f531e7 | Update paths for the consolidated repository layout | ||
|
|
7cec2d614d | Move repository files to the consolidated layout |