diff --git a/.editorconfig b/.editorconfig index cb8852b..620fb2c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,11 @@ # This file is managed by the BHoM platform team and centrally distributed to all active repos. # It is used by CI to enforce code formatting and naming standards via dotnet format. -# Do not edit this file directly in your repository — changes will be overwritten. -# To propose a change, open an issue or PR in BuroHappoldEngineeringAdmin/CI_Toolkit. +# Do not edit this file directly in your repository. Changes will be overwritten. +# To propose a change, open an issue or PR in BHoM/CI_Toolkit. root = true -# All files — space indent (4), LF line endings, trim trailing whitespace, insert final newline, UTF-8. +# All files: space indent (4), LF line endings, trim trailing whitespace, insert final newline, UTF-8. [*] indent_style = space indent_size = 4 @@ -26,14 +26,14 @@ indent_size = 2 trim_trailing_whitespace = false [*.cs] -# IDE1006 — Method names must be PascalCase. +# IDE1006: Method names must be PascalCase. dotnet_naming_rule.methods_pascal_case.symbols = method_symbols dotnet_naming_rule.methods_pascal_case.style = pascal_case_style dotnet_naming_rule.methods_pascal_case.severity = error dotnet_naming_symbols.method_symbols.applicable_kinds = method -# IDE1006 — Parameter names must be camelCase. +# IDE1006: Parameter names must be camelCase. dotnet_naming_rule.parameters_camel_case.symbols = parameter_symbols dotnet_naming_rule.parameters_camel_case.style = camel_case_style dotnet_naming_rule.parameters_camel_case.severity = error @@ -43,17 +43,17 @@ dotnet_naming_symbols.parameter_symbols.applicable_kinds = parameter dotnet_naming_style.pascal_case_style.capitalization = pascal_case dotnet_naming_style.camel_case_style.capitalization = camel_case -# IDE0005 — Remove unnecessary using directives. +# IDE0005: Remove unnecessary using directives. dotnet_diagnostic.IDE0005.severity = warning -# IDE0036 — Enforce consistent modifier order (e.g. public static, not static public). +# IDE0036: Enforce consistent modifier order (e.g. public static, not static public). csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, required, unsafe, volatile, async dotnet_diagnostic.IDE0036.severity = warning -# IDE0044 — Add readonly to fields never reassigned after construction. +# IDE0044: Add readonly to fields never reassigned after construction. dotnet_style_readonly_field = true dotnet_diagnostic.IDE0044.severity = suggestion -# IDE0060 — Remove unused parameters. +# IDE0060: Remove unused parameters. dotnet_code_quality_unused_parameters = all dotnet_diagnostic.IDE0060.severity = warning diff --git a/.github/actions/ci-build/action.yml b/.github/actions/ci-build/action.yml index b5c1509..5b15917 100644 --- a/.github/actions/ci-build/action.yml +++ b/.github/actions/ci-build/action.yml @@ -31,7 +31,8 @@ runs: uses: BHoM/CI_Toolkit/.github/actions/compute-changed-files@develop with: # :(exclude)*AssemblyInfo.cs matches ci-versioning and ci-serialisation, and - # matches what BHoMBot's core check did (Checks/Core.cs:30). + # matches BHoMBot's core check, which excluded AssemblyInfo.cs from the + # changed-file set the same way. # # It is inert today, and is here for consistency rather than for effect. Two # measurements, both worth recording so nobody reads this as protection against diff --git a/.github/actions/ci-compliance/action.yml b/.github/actions/ci-compliance/action.yml index d48f0c6..62c3d36 100644 --- a/.github/actions/ci-compliance/action.yml +++ b/.github/actions/ci-compliance/action.yml @@ -177,6 +177,29 @@ runs: $runnerOutput | ForEach-Object { Write-Host $_ } Write-Host "::endgroup::" + # Which engine produced this verdict. resolve-dependencies already records it, in a + # dependency table further up the job, and someone diagnosing a red is looking here. + # A compliance failure is nearly always either the file or the rules, and nothing + # else on this surface names the rules. The rules track Test_Toolkit's branch by + # decision rather than by default, so the version has to be visible instead. + # + # Built once and emitted twice, deliberately. The job summary is the right place for + # a human, but GitHub exposes no API for job summaries, so a line written only there + # can never be read back and any claim resting on it stays derived. The log can be + # fetched. Keep both, and keep them built from one string so they cannot drift. + $ttSha = '${{ steps.tt-sha.outputs.sha }}' + if ($ttSha.Length -ge 7) { + $ttLine = 'Rules from Test_Toolkit `' + $ttSha.Substring(0, 7) + '`.' + } else { + # Said rather than left blank. An absent SHA means the dependency metadata did not + # record it, which also means the runner cache key lost its Test_Toolkit component + # on this run, so it is worth seeing rather than reading as "no engine involved". + $ttLine = 'Rules from Test_Toolkit, commit not recorded.' + } + + # Backticks are summary markdown. They are noise in a log line, so drop them there. + Write-Host ($ttLine -replace '`', '') + if ($env:GITHUB_STEP_SUMMARY) { $status = if ($exitCode -eq 0) { "passed" } else { "**failed**" } @@ -194,21 +217,9 @@ runs: $summary += $coverage } - # Which engine produced this verdict. resolve-dependencies already records it, in a - # dependency table further up the job, and someone diagnosing a red is looking here. - # A compliance failure is nearly always either the file or the rules, and nothing - # else on this surface names the rules. The rules track Test_Toolkit's branch by - # decision rather than by default, so the version has to be visible instead. - $ttSha = '${{ steps.tt-sha.outputs.sha }}' + # Same string as the log line above, so the two cannot disagree. $summary += "" - if ($ttSha.Length -ge 7) { - $summary += 'Rules from Test_Toolkit `' + $ttSha.Substring(0, 7) + '`.' - } else { - # Said rather than left blank. An absent SHA means the dependency metadata did not - # record it, which also means the runner cache key lost its Test_Toolkit component - # on this run, so it is worth seeing rather than reading as "no engine involved". - $summary += "Rules from Test_Toolkit, commit not recorded." - } + $summary += $ttLine $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 } diff --git a/.github/actions/ci-serialisation/action.yml b/.github/actions/ci-serialisation/action.yml index 0dd41ac..6df5674 100644 --- a/.github/actions/ci-serialisation/action.yml +++ b/.github/actions/ci-serialisation/action.yml @@ -72,8 +72,8 @@ runs: id: changed uses: BHoM/CI_Toolkit/.github/actions/compute-changed-files@develop with: - # AssemblyInfo.cs is excluded to match BHoMBot's serialisation gate - # (Serialisation.cs:35). A version bump cannot change the verdict: the + # AssemblyInfo.cs is excluded to match BHoMBot's serialisation gate, which + # skipped it for the same reason. A version bump cannot change the verdict: the # comparator keys on IToText, which carries no assembly version, and the # round trip happens within a single run. .csproj stays in, unlike # BHoMBot, because a project file can add a reference and so genuinely @@ -117,7 +117,7 @@ runs: if: steps.changed.outputs.count != '0' shell: pwsh run: | - # -clp:ErrorsOnly, as #179 applied to dependency builds: this build is a means + # -clp:ErrorsOnly, as on dependency builds: this build is a means # to an end and its compiler warnings would otherwise become annotations on a # serialisation check. ci-build is where build warnings belong. dotnet restore "${{ steps.solution.outputs.path }}" diff --git a/.github/actions/ci-versioning/action.yml b/.github/actions/ci-versioning/action.yml index 83ddfab..7c08fdd 100644 --- a/.github/actions/ci-versioning/action.yml +++ b/.github/actions/ci-versioning/action.yml @@ -86,10 +86,10 @@ runs: id: changed uses: BHoM/CI_Toolkit/.github/actions/compute-changed-files@develop with: - # AssemblyInfo.cs is excluded to match BHoMBot's Versioning.cs:49: a - # milestone version bump touches it fleet-wide and cannot change the - # outcome, because the result is a function of the built assemblies and - # the datasets, not of the file that changed. Same exclusion as + # AssemblyInfo.cs is excluded to match BHoMBot's versioning check, which + # skipped it too: a milestone version bump touches it fleet-wide and cannot + # change the outcome, because the result is a function of the built + # assemblies and the datasets, not of the file that changed. Same exclusion as # ci-serialisation. The token is wildcarded so it matches at any depth; # a bare 'AssemblyInfo.cs' would anchor to the repo root and match # nothing (see tests/test-changed-file-patterns.sh). @@ -176,7 +176,7 @@ runs: if: steps.changed.outputs.count != '0' shell: pwsh run: | - # -clp:ErrorsOnly for the same reason as #179 on dependency builds: this build + # -clp:ErrorsOnly for the same reason as on dependency builds: this build # is a means to an end, and its compiler warnings become annotations on a # versioning check. Measured on BHoM/Revit_Toolkit#1683: 24 annotations, of # which 2 were versioning findings and 9 were 'ParameterType' is obsolete. @@ -246,9 +246,9 @@ runs: # canonical DLL in it was present in ProgramData. It was removed 2026-08-07 as a # latent fleet-wide outage, on two measurements: # - # 1. It had never executed. Zero of the last 100 BHoM/BHoM_Installer releases and - # zero of the last 30 BuroHappoldEngineering/BuroHappold_Installer releases - # carry IncludedDLLs.txt, so all eight prod runs sampled that day logged + # 1. It had never executed. Zero of the last 100 BHoM/BHoM_Installer releases, + # and zero of the last 30 from the other installer, carry IncludedDLLs.txt, + # so all eight prod runs sampled that day logged # "No release carrying IncludedDLLs.txt ... Skipping fast-fail". # 2. Had a baseline ever appeared it would have failed almost every repo. The # expected set is the whole installer payload (measured: 165 canonical DLLs, @@ -258,8 +258,8 @@ runs: # ETABS_Adapter.dll. Each repo also gets its own resolve-dependencies cache # key, so nothing accumulates across repos. # - # It worked on BHoMBot only because CloneInstaller builds the entire installer set - # before versioning, making its ProgramData fleet-complete. That precondition does + # It worked on BHoMBot only because it builds the entire installer set before + # versioning, making its ProgramData fleet-complete. That precondition does # not hold in per-repo CI, which is the same mistake as the old 3-segment # attribution filter and the Revit mock closure dependency. # @@ -356,8 +356,8 @@ runs: # Ordering matters twice. After resolve-dependencies, because that action saves # the ProgramData cache with an explicit save step, so anything written here # cannot leak into the cached assembly set other checks restore. Before - # Verification.sln, matching BHoMBot's CloneInstaller.cs, which places the mocks - # ahead of the versioning build so [PreviousVersion] attributes are scraped. + # Verification.sln, matching BHoMBot, which places the mocks ahead of the + # versioning build so [PreviousVersion] attributes are scraped. # # Every failure path is a soft skip: without mocks the check still runs and # reports those methods as unverified, which is the pre-existing behaviour. A @@ -458,9 +458,9 @@ runs: # # Re-run the same command the csproj runs, from the same working directory, # differing only in when: after this repo's build and after the Revit mocks, so - # the scrape sees both. BHoMBot gets this for free because CloneInstaller builds - # the whole installer set before rebuilding Versioning_Toolkit ("just to be - # safe"). Re-running is safe: CollectMethodVersioning keys entries on the old + # the scrape sees both. BHoMBot gets this for free because it builds the whole + # installer set before rebuilding Versioning_Toolkit ("just to be safe"). + # Re-running is safe: CollectMethodVersioning keys entries on the old # signature and replaces a same-key entry, so this refreshes rather than # duplicating. Note it only collects attributes whose FromVersion equals # PostBuild.exe's own FileVersion truncated to two segments, so an attribute @@ -540,8 +540,8 @@ runs: # test was ignored, so a declared rename or a MessageForDeleted entry did not # count and the check failed a repo that followed the documented convention. # BHoMBot is immune for the same reason it escaped the attribute-ordering defect: - # CloneInstaller puts every repo, subject included, in one folder, so its - # '..\..\' root already contains the subject. + # it puts every repo, subject included, in one folder, so its '..\..\' root + # already contains the subject. # # The exe is pointed at the workspace's PARENT, because the glob needs the repo # directory to be the first level. resolve-dependencies also junctions each @@ -675,7 +675,7 @@ runs: # The failure mode is deliberately asymmetric. If this finds nothing, no file is written # and no flag is passed, so every finding records VersionConditional=Unknown. An empty # result must not read as "not version-conditional": those are different states and the - # register in CI_Toolkit#173 treats them differently. + # scoring register treats them differently. - name: Collect version-conditional methods id: vercond if: steps.changed.outputs.count != '0' @@ -709,7 +709,7 @@ runs: # present-but-empty file is the assertion "scanned, none found", which lets the # runner record No. Most of the fleet is non-Revit and will land here: 75 of the 92 # alpha and beta repos. Withholding the file for them would leave every versioning - # divergence unscored and the gate in CI_Toolkit#173 unsatisfiable. + # divergence unscored and the scoring gate unsatisfiable. # # The file is withheld only if the scan itself could not run, which is the honest # Unknown case. @@ -767,7 +767,7 @@ runs: # An infrastructure failure and a versioning finding need different action # from the developer, and the job conclusion does not distinguish them. A # dependency build failure previously showed as a red versioning check with - # no indication that versioning never ran. Measured on Revit_UI#696. + # no indication that versioning never ran. Measured on a real pull request. $infraStep = '' if ("${{ steps.deps.conclusion }}" -eq 'failure') { $infraStep = 'Resolve dependencies' } elseif ("${{ steps.build_subject.conclusion }}" -eq 'failure') { $infraStep = 'Build primary repo' } @@ -815,7 +815,7 @@ runs: # "Could not resolve it" is a closure gap: a type the record needs is absent and so # is its namespace, so nothing here could have supplied it. "Could not attribute it" # is an ownership problem: the record resolved, but ownership was inferred from a - # namespace prefix. Different fixes, tracked separately (internal-tickets #32, #31). + # namespace prefix. They need different fixes and are tracked separately. $unresolved = 0 $unattributed = 0 $coverage = $null diff --git a/.github/actions/resolve-dependencies/action.yml b/.github/actions/resolve-dependencies/action.yml index 46fbb60..7f05b13 100644 --- a/.github/actions/resolve-dependencies/action.yml +++ b/.github/actions/resolve-dependencies/action.yml @@ -258,7 +258,7 @@ runs: shell: pwsh run: | # Junctions D:\a\\ -> C:\bhom-deps\ so relative - # HintPaths in .csproj files (e.g. ..\..\Revit_UI\libs\...) resolve + # HintPaths in .csproj files (e.g. ..\..\\libs\...) resolve # when building the caller. No-op for repos without such paths. $workspaceParent = Split-Path $env:GITHUB_WORKSPACE foreach ($dep in Get-ChildItem "C:\bhom-deps" -Directory -ErrorAction SilentlyContinue) { diff --git a/.github/actions/resolve-dependencies/scripts/Build-Dependencies.ps1 b/.github/actions/resolve-dependencies/scripts/Build-Dependencies.ps1 index ee5a816..ea83921 100644 --- a/.github/actions/resolve-dependencies/scripts/Build-Dependencies.ps1 +++ b/.github/actions/resolve-dependencies/scripts/Build-Dependencies.ps1 @@ -118,7 +118,7 @@ foreach ($ownerRepo in $order) { Invoke-BHoMBuild -Target $solution.FullName -Config $Configuration # Build alt configurations for this dependency (Release* only, matching CI config). - # Required so that version-specific assemblies (e.g. Revit_UI_oM_2023.dll) exist + # Required so that version-specific assemblies (e.g. _oM_2023.dll) exist # before the caller repo builds its own alt configs. $depAltConfigFile = Join-Path $repoPath "altConfigs.txt" if (Test-Path $depAltConfigFile) { diff --git a/.github/actions/resolve-dependencies/scripts/Resolve-DependencyGraph.ps1 b/.github/actions/resolve-dependencies/scripts/Resolve-DependencyGraph.ps1 index b6623c0..10b9946 100644 --- a/.github/actions/resolve-dependencies/scripts/Resolve-DependencyGraph.ps1 +++ b/.github/actions/resolve-dependencies/scripts/Resolve-DependencyGraph.ps1 @@ -129,7 +129,7 @@ function Clone-And-Checkout([string]$ownerRepo, [string]$ref) { # compared two legs that shared the same dependency code. A regression introduced on the # dependency side appeared in both and cancelled out. # - # This is BHoMBot's shape, deliberately. LoadDependencies (CodeBuild_Engine) cloned if + # This is BHoMBot's shape, deliberately. Its dependency loader cloned if # absent and then always called CheckoutBranch with the branch as an explicit argument, and # ResetBuiltRepos() cleared the memo that would otherwise have skipped the re-checkout. The # divergence from BHoMBot is that the memo has no equivalent here and needs none: there is diff --git a/.github/scripts/Select-AltConfigs.ps1 b/.github/scripts/Select-AltConfigs.ps1 index 19b857a..ecb24b8 100644 --- a/.github/scripts/Select-AltConfigs.ps1 +++ b/.github/scripts/Select-AltConfigs.ps1 @@ -20,9 +20,9 @@ function Select-AltConfigs { - Real altConfigs.txt files carry both. Every Revit-family repo sampled lists five Debug20NN and five Release20NN entries. - - The installer ships Release* only. BuroHappold_Installer's own - IncludedRepos/altConfigs.txt contains Release entries exclusively, no Debug. - So building Debug20NN in CI verifies configurations that are never shipped. + - The installer ships Release* only. Its own IncludedRepos/altConfigs.txt + contains Release entries exclusively, no Debug. So building Debug20NN in + CI verifies configurations that are never shipped. - resolve-dependencies already filters the same way for dependency repos (Build-Dependencies.ps1), and caller Debug builds succeeded in production against a Release-only dependency closure, so the two sides were not diff --git a/.github/scripts/tests/ci-compliance-action.Tests.ps1 b/.github/scripts/tests/ci-compliance-action.Tests.ps1 index ea35553..694ba77 100644 --- a/.github/scripts/tests/ci-compliance-action.Tests.ps1 +++ b/.github/scripts/tests/ci-compliance-action.Tests.ps1 @@ -60,6 +60,24 @@ Describe 'ci-compliance action.yml' { It 'says so when the commit was not recorded, rather than omitting the line' { $text | Should -Match 'commit not recorded' } + + # GitHub exposes no API for job summaries, so a line written only there can never be + # read back and any claim resting on it stays derived rather than observed. The log + # can be fetched. Asserted by position rather than by presence, because moving the + # emission back inside the summary guard would still match a plain text search and + # would silently undo the only thing that makes this checkable. + It 'emits the engine line to the log as well as the summary' { + $logEmit = $text.IndexOf('Write-Host ($ttLine') + $summaryGuard = $text.IndexOf('if ($env:GITHUB_STEP_SUMMARY)') + + $logEmit | Should -BeGreaterThan -1 -Because 'the summary is unreadable by any API' + $summaryGuard | Should -BeGreaterThan -1 + $logEmit | Should -BeLessThan $summaryGuard -Because 'inside the guard it would not reach the log' + } + + It 'builds the log line and the summary line from one string' { + $text | Should -Match '\$summary \+= \$ttLine' -Because 'two independently built strings drift' + } } Context 'a self-skip says so' { diff --git a/.github/scripts/tests/test-changed-file-patterns.sh b/.github/scripts/tests/test-changed-file-patterns.sh index 8fbbacb..335f546 100644 --- a/.github/scripts/tests/test-changed-file-patterns.sh +++ b/.github/scripts/tests/test-changed-file-patterns.sh @@ -46,8 +46,8 @@ FIXTURE_PATHS=( ".ci/DataSets/Area/capsdeep.json" # nested but capital S: isolates casing "src/datasets/lower/low.json" # lowercase, already matched "a/mydatasets.json" # substring in filename, already matched - "Datasets/root-direct.json" # root-level, e.g. CFD_Toolkit - "Datasets/Area/root-nested.json" # root-level nested, e.g. BuroHappold_Datasets + "Datasets/root-direct.json" # root-level, directly under Datasets/ + "Datasets/Area/root-nested.json" # root-level nested, e.g. a *_Datasets repo "DataSets/Caps/caps.json" # root + capital S, e.g. LifeCycleAssessment_Toolkit "DATASETS/shouty/shout.json" # upper case, defensive "datasets.json" # root-level file @@ -57,7 +57,6 @@ FIXTURE_PATHS=( # BH.oM.Data.Library.Dataset documents, so IsValidDataset errors on every one. # Measured 2026-09-07: DatasetComplianceRunner exits 1 on this file and 0 on a # real library dataset. Excluded rather than teaching the runner a second shape. - # See BHoM/internal-tickets#36. ".ci/code/Versioning_Test/Datasets/9.2/Objects.json" ".ci/code/Versioning_Test/Datasets/9.1/Methods.json" # The project-directory case. Here the substring comes from neither the filename @@ -66,9 +65,10 @@ FIXTURE_PATHS=( # for. These are BHoM versioning upgrade maps, {"Dataset":{"ToNew":..,"ToOld":..}}, # read by BHoM_Engine Versioning_Engine/Query/DatasetToNewPaths.cs from # %ProgramData%\BHoM\Upgrades. They carry no _t and cannot be Dataset documents. - # The first is FAILING IN PRODUCTION TODAY and the exclusion below deliberately - # does NOT cover it. See BHoM/internal-tickets#43. - "BuroHappold_Datasets/Versioning_93.json" + # Both sit in a project folder named *_Datasets, which is what supplies the + # 'datasets' substring the base selector needs, and the exclusion below + # deliberately does NOT cover them. + "Example_Datasets/Versioning_93.json" "BHoM_Datasets/Versioning_93.json" # Project-compliance inputs. "AssemblyInfo.cs" # repo root: the only thing the old token matched @@ -279,14 +279,14 @@ else assert_not_matches "dataset" "$pat" ".ci/code/Versioning_Test/Datasets/9.2/Objects.json" assert_not_matches "dataset" "$pat" ".ci/code/Versioning_Test/Datasets/9.1/Methods.json" # CHARACTERISATION, NOT APPROVAL. The project-directory case is still selected, - # and the first of these is a live production failure. Asserted positively for - # two reasons: it proves the exclusion above is one directory layout rather than + # and this layout has occurred in practice. Asserted positively for two + # reasons: it proves the exclusion above is one directory layout rather than # a class-wide "stop checking non-datasets", and it means anyone who later widens # the pattern into this case, or re-anchors the selector onto the data directory, # has to flip a visible assertion instead of changing fleet-wide scope silently. # If you are here because you re-anchored the selector: flipping these two to # assert_not_matches is the intended outcome, not a regression. - assert_matches "dataset" "$pat" "BuroHappold_Datasets/Versioning_93.json" + assert_matches "dataset" "$pat" "Example_Datasets/Versioning_93.json" assert_matches "dataset" "$pat" "BHoM_Datasets/Versioning_93.json" done <<< "$dataset_patterns" fi @@ -324,7 +324,7 @@ else # project-directory case was ever in scope here. Asserted so that re-anchoring # this pattern cannot silently pull them in. assert_not_matches "dataset-tests" "$dt_pattern" ".ci/code/Versioning_Test/Datasets/9.2/Objects.json" - assert_not_matches "dataset-tests" "$dt_pattern" "BuroHappold_Datasets/Versioning_93.json" + assert_not_matches "dataset-tests" "$dt_pattern" "Example_Datasets/Versioning_93.json" assert_not_matches "dataset-tests" "$dt_pattern" "BHoM_Datasets/Versioning_93.json" # The two patterns must stay distinct. If someone re-unifies them this fails. @@ -376,7 +376,8 @@ ver_pattern=$(grep -h "^[[:space:]]*patterns:" \ if [ -z "$ver_pattern" ]; then fail "ci-versioning: no pattern found" else - # Mirrors BHoMBot Versioning.cs:49. One consumer now: the skip decision + # Mirrors BHoMBot, whose versioning check excluded AssemblyInfo.cs the same + # way. One consumer now: the skip decision # (count). It previously also fed the deletion-only guard in # Assert-VersioningPrerequisites.ps1, but that fast-fail was removed on # 2026-08-07 because its expected set was the whole installer payload while diff --git a/.github/workflows/test-tools.yml b/.github/workflows/test-tools.yml index 6b675c3..a860820 100644 --- a/.github/workflows/test-tools.yml +++ b/.github/workflows/test-tools.yml @@ -3,8 +3,9 @@ # lint-workflows covers the YAML and the bash pathspec suite, but its paths filter # lists only .github/workflows, .github/actions, .github/scripts and templates. Nothing # matched tools/**, so a pull request touching only the runners received no checks at -# all: PR #131 reported zero. That left every runner regression test inert, including the -# one added in #131 to stop the System.Drawing.Common reference being dropped as unused. +# all: one such pull request reported zero checks. That left every runner regression test +# inert, including the one added to stop the System.Drawing.Common reference being +# dropped as unused. # # Two jobs and two ComplianceRunner test projects, deliberately separate. # diff --git a/templates/BHE/ci-beta.yml b/templates/BHE/ci-beta.yml index 91c401c..26bc04d 100644 --- a/templates/BHE/ci-beta.yml +++ b/templates/BHE/ci-beta.yml @@ -58,13 +58,12 @@ jobs: # BH.oM.Data.Library.Dataset documents, so IsValidDataset errors on every # one of them. Measured: DatasetComplianceRunner exits 1 on 9.2/Objects.json # and 0 on a real library dataset. Excluded rather than teaching the runner - # a second shape, because that would touch Test_Toolkit. See - # BHoM/internal-tickets#36. + # a second shape, because that would touch Test_Toolkit. # # This excludes one directory layout, not a class of file. The selector is # substring-based, not dataset-aware: '*' crosses '/' without :(glob), so # the substring can come from a filename, a data directory, or a project - # directory named *_Datasets. See BHoM/internal-tickets#43. + # directory named *_Datasets. patterns: ':(icase)*datasets*.json :(exclude,icase)*Versioning_Test/Datasets/*' app_id: ${{ secrets.BHOM_APP_ID }} private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} diff --git a/templates/BHoM/ci-beta.yml b/templates/BHoM/ci-beta.yml index 21d53af..f811539 100644 --- a/templates/BHoM/ci-beta.yml +++ b/templates/BHoM/ci-beta.yml @@ -74,13 +74,12 @@ jobs: # BH.oM.Data.Library.Dataset documents, so IsValidDataset errors on every # one of them. Measured: DatasetComplianceRunner exits 1 on 9.2/Objects.json # and 0 on a real library dataset. Excluded rather than teaching the runner - # a second shape, because that would touch Test_Toolkit. See - # BHoM/internal-tickets#36. + # a second shape, because that would touch Test_Toolkit. # # This excludes one directory layout, not a class of file. The selector is # substring-based, not dataset-aware: '*' crosses '/' without :(glob), so # the substring can come from a filename, a data directory, or a project - # directory named *_Datasets. See BHoM/internal-tickets#43. + # directory named *_Datasets. patterns: ':(icase)*datasets*.json :(exclude,icase)*Versioning_Test/Datasets/*' app_id: ${{ secrets.BHOM_APP_ID }} private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} diff --git a/tools/ComplianceRunner/src/ComplianceRunner/ComplianceRunner.cs b/tools/ComplianceRunner/src/ComplianceRunner/ComplianceRunner.cs index 915e571..a574062 100644 --- a/tools/ComplianceRunner/src/ComplianceRunner/ComplianceRunner.cs +++ b/tools/ComplianceRunner/src/ComplianceRunner/ComplianceRunner.cs @@ -72,7 +72,7 @@ static int Main(string[] args) resultForThisFile = BH.Engine.Test.CodeCompliance.Compute.CheckAssemblyInfo(file, orgUrl); // Remap absolute location paths back to the relative file path so - // annotations point to the correct diff line, mirroring BHoMBot ProjectCompliance.cs. + // annotations point to the correct diff line, mirroring BHoMBot's project check. // Note: .OfType() silently drops any ITestInformation that is not exactly // BH.oM.Test.CodeCompliance.Error. Currently safe, because the project engine only emits // Error-typed findings, but revisit if new finding sub-types are introduced. @@ -104,7 +104,7 @@ static int Main(string[] args) resultForThisFile = BH.Engine.Test.CodeCompliance.Compute.RunChecks(file.Replace('/', '\\'), checkType); // Group findings one-per-line and remap the path to the diff-relative file, - // mirroring BHoMBot's GroupInformation -> GroupErrors pipeline (code/copyright/ + // mirroring how BHoMBot grouped findings and then remapped them (code/copyright/ // documentation only; project has its own remap above). GroupErrors concatenates // each finding's FullMessage() into the grouped Error.Message, so downstream these // annotations are built from that message directly (see the preformatted branch in @@ -139,7 +139,7 @@ static int Main(string[] args) // // Project findings use IFullMessage, which appends the " - For more information // see " documentation suffix. This is a deliberate, documented divergence from - // BHoMBot: BHoMBot's ProjectCompliance emitted the bare Error.Message with no suffix, + // BHoMBot, whose project check emitted the bare Error.Message with no suffix, // even though it carried the DocumentationLink. Every other compliance check surfaces // that link (via GroupErrors -> FullMessage), and the project engine attaches a // DocumentationLink to every finding specifically so it can be shown, so BHoMBot's diff --git a/tools/ComplianceRunner/src/DatasetComplianceRunner/DatasetComplianceRunner.cs b/tools/ComplianceRunner/src/DatasetComplianceRunner/DatasetComplianceRunner.cs index 97db7fe..d781e6f 100644 --- a/tools/ComplianceRunner/src/DatasetComplianceRunner/DatasetComplianceRunner.cs +++ b/tools/ComplianceRunner/src/DatasetComplianceRunner/DatasetComplianceRunner.cs @@ -15,7 +15,7 @@ static int Main(string[] args) // [file2.json ...] // // Only processes .json files whose path contains "datasets" (case-insensitive), - // mirroring BHoMBot's DatasetCompliance filtering. + // mirroring BHoMBot's dataset filtering. var (outputFormat, sarifFilePath, files) = ArgParser.ParseDataset(args); if (files == null || files.Count == 0) { diff --git a/tools/ComplianceRunner/src/Shared/Compliance.Shared/AnnotationConvert.cs b/tools/ComplianceRunner/src/Shared/Compliance.Shared/AnnotationConvert.cs index 2580c5d..4e5f03a 100644 --- a/tools/ComplianceRunner/src/Shared/Compliance.Shared/AnnotationConvert.cs +++ b/tools/ComplianceRunner/src/Shared/Compliance.Shared/AnnotationConvert.cs @@ -31,9 +31,10 @@ public static class AnnotationConvert public static Annotation ToAnnotationEquivalent(this ITestInformation info, bool messageIsPreformatted = false) { var ann = new Annotation(); - // Three-way mapping mirrors BHoMBot's AnnotationLevel.Failure / Warning / Notice. - // Non-Error non-Warning statuses (typically Pass with metadata, used by - // DatasetCompliance to surface dataset origin) become Notice, not Warning. + // Three-way mapping mirrors the GitHub annotation levels failure / warning / + // notice, which is what BHoMBot mapped onto too. Non-Error non-Warning statuses + // (typically Pass with metadata, used by the dataset check to surface dataset + // origin) become Notice, not Warning. ann.Level = info.Status switch { TestStatus.Error => "failure", @@ -79,7 +80,7 @@ public static Annotation ToAnnotationEquivalent(this ITestInformation info, bool /// FullMessage uses one floor for two things: it blanks the finding's own message when the /// finding is less severe than the floor, and it drops inner results below it. So the floor /// cannot simply be raised to Warning for everything, because that would empty a Pass-level - /// notice, which DatasetCompliance uses to surface dataset provenance. Holding the floor at + /// notice, which the dataset check uses to surface dataset provenance. Holding the floor at /// Pass for a Pass and at Warning otherwise drops passing inner results while leaving /// warnings and errors intact, matching the filter DatasetTestRunner already applies to its /// own top level. diff --git a/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs b/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs index 7bb0044..4b7ddab 100644 --- a/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs +++ b/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs @@ -34,7 +34,7 @@ public static bool IsRelevantFile(string file, string checkType) /// /// /// The directory test is anchored on a whole path segment. It used to ask whether the - /// lowercased path contained "datasets" anywhere, which is BHoMBot's DatasetCompliance + /// lowercased path contained "datasets" anywhere, which is BHoMBot's dataset /// filter, x.ToLower().Contains("datasets") && x.EndsWith(".json"). That /// selects a file because of a project directory named *_Datasets, which holds code rather /// than datasets: both dataset repositories carry one beside the real Datasets directory, diff --git a/tools/ComplianceRunner/tests/Compliance.Tests/Integration/PathspecFilterPairingTests.cs b/tools/ComplianceRunner/tests/Compliance.Tests/Integration/PathspecFilterPairingTests.cs index a96dc1d..cdb4788 100644 --- a/tools/ComplianceRunner/tests/Compliance.Tests/Integration/PathspecFilterPairingTests.cs +++ b/tools/ComplianceRunner/tests/Compliance.Tests/Integration/PathspecFilterPairingTests.cs @@ -20,7 +20,7 @@ /// /// These tests are written to PASS against today's behaviour. They record the disagreement so /// it is visible in the suite. Whether the fix narrows the pathspec or widens the filter is -/// open: BHoMBot used EndsWith("AssemblyInfo.cs") (ProjectCompliance.cs:33), so widening the +/// open: BHoMBot's project compliance matched on EndsWith("AssemblyInfo.cs"), so widening the /// filter restores the older semantics, and line 20 of that same FileFilterTests file asserts /// the narrower one deliberately. Where an assertion would change under one of those two /// answers, the comment says what it should become and under which answer. diff --git a/tools/ComplianceRunner/tests/Compliance.Tests/Unit/AnnotationInnerResultsTests.cs b/tools/ComplianceRunner/tests/Compliance.Tests/Unit/AnnotationInnerResultsTests.cs index 81a6939..4f9c5b3 100644 --- a/tools/ComplianceRunner/tests/Compliance.Tests/Unit/AnnotationInnerResultsTests.cs +++ b/tools/ComplianceRunner/tests/Compliance.Tests/Unit/AnnotationInnerResultsTests.cs @@ -74,7 +74,7 @@ public void InnerResults_MessageStaysSmallOnALargeFixture() "a 501-entry fixture with one failure must not produce a multi-kilobyte message"); } - // A Pass-status finding still has to render its own message. DatasetCompliance surfaces + // A Pass-status finding still has to render its own message. The dataset check surfaces // dataset provenance that way, and FullMessage returns "" for a result less severe than the // floor it is given, so the floor cannot be raised unconditionally. [Test] diff --git a/tools/ComplianceRunner/tests/Compliance.Tests/Unit/OutputEmitterTests.cs b/tools/ComplianceRunner/tests/Compliance.Tests/Unit/OutputEmitterTests.cs index bb2fd98..4c8d8c8 100644 --- a/tools/ComplianceRunner/tests/Compliance.Tests/Unit/OutputEmitterTests.cs +++ b/tools/ComplianceRunner/tests/Compliance.Tests/Unit/OutputEmitterTests.cs @@ -8,7 +8,7 @@ /// /// GitHub consumes file/line/col as annotation metadata and strips them from the rendered log /// line, so the offending file has to be present in the title property and in the message text -/// for a failure to be actionable from the job log. See CI_Toolkit_Proxy issue #22. +/// for a failure to be actionable from the job log. /// [TestFixture] public class OutputEmitterTests diff --git a/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs b/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs index 3d7f163..1b154d0 100644 --- a/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs +++ b/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs @@ -41,9 +41,9 @@ public class IsDatasetFileTests [TestCase("DataSets/foo.json", ExpectedResult = true)] // root-level, mixed case [TestCase("DataSets/LCA/deep/x.json", ExpectedResult = true)] // root-level, nested - // Versioning upgrade maps. The live case is a project directory supplying the - // "datasets" substring, which is how BuroHappold_Datasets/Versioning_93.json and - // BHoM_Datasets/Versioning_100.json get selected at all. + // Versioning upgrade maps. The case seen in practice is a project directory + // supplying the "datasets" substring, which is how Example_Datasets/Versioning_93.json + // and BHoM_Datasets/Versioning_100.json get selected at all. [TestCase("BHoM_Datasets/Versioning_93.json", ExpectedResult = false)] [TestCase("BHoM_Datasets/Versioning_100.json", ExpectedResult = false)] [TestCase(@"BHoM_Datasets\Versioning_93.json", ExpectedResult = false)] // backslash separators @@ -76,9 +76,9 @@ public class IsDatasetFileTests // The anchoring. A project directory named *_Datasets supplies the substring and holds // code, not datasets; both dataset repositories carry one beside the real Datasets - // directory. These are the #43 cases stated without relying on the upgrade-map rule, - // so a failure here says the anchoring moved rather than the name test. - [TestCase("BuroHappold_Datasets/Rules.json", ExpectedResult = false)] + // directory. These state that case without relying on the upgrade-map rule, so a + // failure here says the anchoring moved rather than the name test. + [TestCase("Example_Datasets/Rules.json", ExpectedResult = false)] [TestCase("BHoM_Datasets/Anything.json", ExpectedResult = false)] [TestCase(@"BHoM_Datasets\Anything.json", ExpectedResult = false)] // backslash separators [TestCase("mydatasets/foo.json", ExpectedResult = false)] @@ -102,10 +102,10 @@ public bool IsDatasetFile(string file) // The upgrade-map rule on its own, so a failure says which of the two predicates moved. // These paths carry no "datasets" substring, so IsDatasetFile rejects them anyway and // could not distinguish the two. - [TestCase("Tagging_oM/Versioning_93.json", ExpectedResult = true)] - [TestCase("Tagging_oM/Versioning_Rules.json", ExpectedResult = false)] - [TestCase("Versioning_93.json", ExpectedResult = true)] // no directory at all - [TestCase("Structure_oM/Versioning_93.txt", ExpectedResult = true)] // extension is IsDatasetFile's job + [TestCase("Structure_oM/Versioning_93.json", ExpectedResult = true)] + [TestCase("Structure_oM/Versioning_Rules.json", ExpectedResult = false)] + [TestCase("Versioning_93.json", ExpectedResult = true)] // no directory at all + [TestCase("Structure_oM/Versioning_93.txt", ExpectedResult = true)] // extension is IsDatasetFile's job public bool IsVersioningUpgradeMap(string file) => FileFilter.IsVersioningUpgradeMap(file); } diff --git a/tools/SerialiserRunner/src/SerialiserRunner.Tests/HostDependencyTests.cs b/tools/SerialiserRunner/src/SerialiserRunner.Tests/HostDependencyTests.cs index cdcc111..3c39fdb 100644 --- a/tools/SerialiserRunner/src/SerialiserRunner.Tests/HostDependencyTests.cs +++ b/tools/SerialiserRunner/src/SerialiserRunner.Tests/HostDependencyTests.cs @@ -26,8 +26,8 @@ public void Bitmap_IsResolvable_SoTheDynamicSerialiserBinderCanBind() // Revit_X_oM_20NN reaches PresentationCore, and BH.oM.Forms reaches System.Windows.Forms. // On a plain net8.0 host those resolve to nothing, so asm.GetTypes() throws // ReflectionTypeLoadException, BHoM_Engine's ExtractTypes swallows it, and the entire - // assembly registers zero types. Measured: 41 types in Revit_MechanicalPlumbing_oM_2022, - // 4 unloadable, 0 registered. Under net8.0-windows the same assembly returns all 41. + // assembly registers zero types. Measured on one year-suffixed Revit oM assembly: + // 41 types, 4 unloadable, 0 registered. Under net8.0-windows it returns all 41. // // Loaded by name so this fails rather than refusing to compile if the TFM is reverted. [Theory] diff --git a/tools/SerialiserRunner/src/SerialiserRunner.Tests/RunCommandTests.cs b/tools/SerialiserRunner/src/SerialiserRunner.Tests/RunCommandTests.cs index 59aec3a..ce183ab 100644 --- a/tools/SerialiserRunner/src/SerialiserRunner.Tests/RunCommandTests.cs +++ b/tools/SerialiserRunner/src/SerialiserRunner.Tests/RunCommandTests.cs @@ -63,8 +63,8 @@ public void StandardSuffixes_ReturnTrue(string path) } [Theory] - // Legacy parity: BHoMBot's SerialiserProcess.LoadAssemblies only loaded the - // four suffixes below. Revit year-versioned DLLs match none of them, so they + // Legacy parity: BHoMBot only loaded the four suffixes below when it + // gathered assemblies. Revit year-versioned DLLs match none of them, so they // are NOT loadable — matching legacy behaviour (the Revit expansion is removed). [InlineData(@"C:\assemblies\Revit_Toolkit2024.dll")] [InlineData(@"C:\assemblies\Revit_Toolkit2023.dll")] diff --git a/tools/SerialiserRunner/src/SerialiserRunner/Commands/CompareCommand.cs b/tools/SerialiserRunner/src/SerialiserRunner/Commands/CompareCommand.cs index 2ed559b..d2d6313 100644 --- a/tools/SerialiserRunner/src/SerialiserRunner/Commands/CompareCommand.cs +++ b/tools/SerialiserRunner/src/SerialiserRunner/Commands/CompareCommand.cs @@ -44,8 +44,8 @@ public static CompareResult Compare(SerialiserResult baseline, SerialiserResult IsBaselineUnusable: true); // Legacy comparison is COUNT-aware (List semantics), keyed on each failing - // entry's Description. Reproduced verbatim from BHoMBot's - // InformationIsEqual / InformationIsLessAndBetter (see the two helpers). + // entry's Description. Reproduced verbatim from BHoMBot's two comparison + // rules, equality and strict improvement (see the two helpers below). List main = baseline.Failures; List branchInfo = branch.Failures; @@ -84,7 +84,7 @@ public static CompareResult Compare(SerialiserResult baseline, SerialiserResult return new CompareResult(true, summary); } - // BHoMBot Serialisation.InformationIsEqual: equal entry count AND every branch + // Matches BHoMBot's equality rule: equal entry count AND every branch // failure's Description exists on the baseline (existence, not multiplicity). private static bool InformationIsEqual(List main, List branch) { @@ -93,7 +93,7 @@ private static bool InformationIsEqual(List main, List return branch.All(b => main.Any(m => m.Description == b.Description)); } - // BHoMBot Serialisation.InformationIsLessAndBetter: strictly fewer entries than + // Matches BHoMBot's strict-improvement rule: strictly fewer entries than // the baseline AND every branch failure's Description exists on the baseline. private static bool InformationIsLessAndBetter(List main, List branch) { diff --git a/tools/SerialiserRunner/src/SerialiserRunner/Commands/RunCommand.cs b/tools/SerialiserRunner/src/SerialiserRunner/Commands/RunCommand.cs index 8c9ca55..e9c2fa0 100644 --- a/tools/SerialiserRunner/src/SerialiserRunner/Commands/RunCommand.cs +++ b/tools/SerialiserRunner/src/SerialiserRunner/Commands/RunCommand.cs @@ -129,9 +129,8 @@ private static (SerialiserResult result, bool isConfigError) InvokeTests(string catch (Exception ex) { // A Verify method that throws contributes zero failures, so the run is - // incomplete and must fail. BHoMBot swallows this silently - // (SerialiserProcess/Program.cs HandleFile) and that is exactly how 1113 - // object types went unverified under a green check. + // incomplete and must fail. BHoMBot swallows this silently, and that is + // exactly how 1113 object types went unverified under a green check. // // Record it and carry on rather than returning here. Returning discarded // every failure, Event and population count the other Verify methods had diff --git a/tools/VersioningRunner/src/VersioningRunner.Tests/AssemblyLoadOrderTests.cs b/tools/VersioningRunner/src/VersioningRunner.Tests/AssemblyLoadOrderTests.cs index 30f6d8d..3b647a3 100644 --- a/tools/VersioningRunner/src/VersioningRunner.Tests/AssemblyLoadOrderTests.cs +++ b/tools/VersioningRunner/src/VersioningRunner.Tests/AssemblyLoadOrderTests.cs @@ -6,7 +6,7 @@ namespace VersioningRunner.Tests // Load order is not cosmetic. ProbeDeclaringType takes its verdict from the first // loaded assembly that yields the declaring type, and 42 type names in the fleet are // defined by more than one assembly, so the enumeration order decides the - // classification for those. See CI_Toolkit#161. + // classification for those. public class AssemblyLoadOrderTests { private static string[] Names(IEnumerable paths) diff --git a/tools/VersioningRunner/src/VersioningRunner.Tests/ClosureReclassificationTests.cs b/tools/VersioningRunner/src/VersioningRunner.Tests/ClosureReclassificationTests.cs index 791a1e0..dfe8b63 100644 --- a/tools/VersioningRunner/src/VersioningRunner.Tests/ClosureReclassificationTests.cs +++ b/tools/VersioningRunner/src/VersioningRunner.Tests/ClosureReclassificationTests.cs @@ -12,8 +12,8 @@ namespace VersioningRunner.Tests // the closure explicitly so the rule is actually exercised. public class ClosureReclassificationTests { - private const string ModelQaEvent = - "Method TryGetValueFromSource from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Revit.Engine.Core.Compute, Revit_ModelQA_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; + private const string ForeignAsmEvent = + "Method TryGetValueFromSource from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Revit.Engine.Core.Compute, Example_Foreign_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; private const string Config2024Event = "Method ProjectParameter from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Revit.Engine.Core.Create, Revit_Core_Engine_2024, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; @@ -71,7 +71,7 @@ private static (VersioningResult Result, FailureDiagnostic Diag) Run( public void ForeignAssemblyAnsweredByAnother_IsUnverified() { var (result, d) = Run( - Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ModelQaEvent), + Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ForeignAsmEvent), Answered("Revit_Core_Engine_2022"), Closure(loaded: ["Revit_Core_Engine_2022"], subject: ["Revit_Core_Engine_2022", "Revit_oM"])); @@ -127,7 +127,7 @@ public void DeclaringAssemblyPresent_IsUntouched() public void NoClosureSupplied_IsUntouched() { var (result, d) = Run( - Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ModelQaEvent), + Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ForeignAsmEvent), Answered("Revit_Core_Engine_2022"), closure: null); @@ -154,7 +154,7 @@ public void SubjectFamilyWithNoLoadedVariant_StaysReal() public void AlreadyUnverified_IsNotRelabelled() { var (result, d) = Run( - Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ModelQaEvent), + Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ForeignAsmEvent), (_, _, _) => ("RevitAPI", ClassificationPath.SignatureBlockerOutsideBHoM, new[] { "Revit_Core_Engine_2022" }), Closure(loaded: ["Revit_Core_Engine_2022"], subject: ["Revit_Core_Engine_2022"])); @@ -184,11 +184,11 @@ public void StripConfigSuffix_CollapsesOnlyA20xxTail(string input, string expect public void MultipleMethodEvents_TheFirstNamedAssemblyDecides() { var (result, d) = Run( - Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ModelQaEvent, SubjectAsmEvent), + Tree("BH.Revit.Engine.Core.Compute.TryGetValueFromSource", ForeignAsmEvent, SubjectAsmEvent), Answered("Revit_Core_Engine_2022"), Closure(loaded: ["Revit_Core_Engine_2022"], subject: ["Revit_Core_Engine_2022"])); - Assert.Equal("Revit_ModelQA_Engine_2022", d.DeclaringAssembly); + Assert.Equal("Example_Foreign_Engine_2022", d.DeclaringAssembly); Assert.Equal(0, result.FailureCount); Assert.Equal(ClassificationPath.ForeignDeclaringAssembly, d.Path); } diff --git a/tools/VersioningRunner/src/VersioningRunner.Tests/HostDependencyTests.cs b/tools/VersioningRunner/src/VersioningRunner.Tests/HostDependencyTests.cs index 4238848..434dd82 100644 --- a/tools/VersioningRunner/src/VersioningRunner.Tests/HostDependencyTests.cs +++ b/tools/VersioningRunner/src/VersioningRunner.Tests/HostDependencyTests.cs @@ -21,14 +21,11 @@ public class HostDependencyTests // is silently discarded: the check reports a green it has not earned, which is // the exact failure shape this whole check was rebuilt to remove. // - // Measured against the real 547-assembly closure with the Revit mocks present, - // so the WPF cause is isolated from the RevitAPIUI one: - // Revit_MechanicalPlumbing net8.0: 115 null, 35 unreadable, 15 namespaces - // net8.0-windows: 0, 0, 17 - // Revit_ElementRelationships net8.0: 95 null, 20 unreadable, 4 namespaces - // net8.0-windows: 0, 0, 5 - // Revit_Tagging net8.0: 75 null, 10 unreadable, 7 namespaces - // net8.0-windows: 0, 0, 8 + // Measured on a real closure with the Revit mocks present, which isolates the + // WPF cause from the RevitAPIUI one. Every Revit tool's UI assembly reported + // most of its types as null on net8.0 and a further tranche as unreadable; + // on net8.0-windows the same assemblies reported none of either and gained + // namespaces. The project file carries the same reasoning for the TFM itself. // // Loaded by name rather than referenced in code, so this fails rather than // refusing to compile if the TFM is reverted. diff --git a/tools/VersioningRunner/src/VersioningRunner.Tests/RunCommandTests.cs b/tools/VersioningRunner/src/VersioningRunner.Tests/RunCommandTests.cs index 1f38105..047d48c 100644 --- a/tools/VersioningRunner/src/VersioningRunner.Tests/RunCommandTests.cs +++ b/tools/VersioningRunner/src/VersioningRunner.Tests/RunCommandTests.cs @@ -394,8 +394,8 @@ public void MethodDescription_IsAttributed() { // Versioning_Toolkit's DescriptionFromJson yields "DeclaringType.MethodName". Assert.True(RunCommand.IsFromSubjectNamespace( - "BH.Revit.Engine.MechanicalPlumbing.Compute.SpaceRoomPairs", - Namespaces("BH.Revit.Engine.MechanicalPlumbing"))); + "BH.Example.Engine.Area.Compute.SpaceRoomPairs", + Namespaces("BH.Example.Engine.Area"))); } [Fact] @@ -405,7 +405,7 @@ public void SiblingNamespace_IsNotAttributed() // BH.oM.Adapters and attributed every adapter's types to every repo. var subject = Namespaces("BH.oM.Adapters.File"); Assert.False(RunCommand.IsFromSubjectNamespace("BH.oM.Adapters.ETABS.Elements.Pier", subject)); - Assert.False(RunCommand.IsFromSubjectNamespace("BH.oM.Adapters.Sharepoint.File", subject)); + Assert.False(RunCommand.IsFromSubjectNamespace("BH.oM.Adapters.Example.File", subject)); Assert.False(RunCommand.IsFromSubjectNamespace("BH.oM.Structure.Elements.Bar", subject)); } @@ -519,7 +519,7 @@ public void SubjectListNamingNothingLoaded_YieldsNoNamespacesRatherThanTheClosur private const string RevitCause = "Type Autodesk.Revit.DB.Document, RevitAPI, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null failed to deserialise."; private const string MethodCause = - "Method ApplyDuctInsulation from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Revit.Engine.MechanicalPlumbing.Compute, Revit_MechanicalPlumbing_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; + "Method ApplyAreaOffset from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Example.Engine.Area.Compute, Example_Area_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; // The object-record shapes, also verbatim from a real run (BHoM/BHoM 33849699768). // A nested fragment from a downstream toolkit names itself here. @@ -668,9 +668,9 @@ public void NamedFailingType_ParsesAllThreeEventShapes() [Fact] public void UnknownShapeWithUnknownVersion_StillYieldsTheBareType() { - Assert.Equal("BH.oM.Adapters.Sharepoint.Requests.OrCondition", + Assert.Equal("BH.oM.Adapters.Example.Requests.OrCondition", RunCommand.NamedFailingType( - "The type BH.oM.Adapters.Sharepoint.Requests.OrCondition from version unknown is unknown -> data returned as custom objects.")); + "The type BH.oM.Adapters.Example.Requests.OrCondition from version unknown is unknown -> data returned as custom objects.")); } [Fact] @@ -679,7 +679,7 @@ public void UnresolvableLeaf_IsCollectedSeparatelyAndNotFailed() var leaf = new FakeTestInfo { Status = "Error", - Description = "BH.Revit.Engine.MechanicalPlumbing.Compute.ApplyDuctInsulation", + Description = "BH.Example.Engine.Area.Compute.ApplyAreaOffset", Message = "Error: Returned null from json.", Information = [ new FakeEventMessage { Message = RevitCause }, @@ -726,8 +726,8 @@ public void LeafNamingABHoMTypeFailure_StillFails() public void MethodEvent_YieldsDeclaringTypeAndMethodName() { var (type, method) = RunCommand.ParseMethodEvent(MethodCause); - Assert.Equal("BH.Revit.Engine.MechanicalPlumbing.Compute", type); - Assert.Equal("ApplyDuctInsulation", method); + Assert.Equal("BH.Example.Engine.Area.Compute", type); + Assert.Equal("ApplyAreaOffset", method); } [Fact] @@ -755,17 +755,17 @@ public void MethodNameThatDoesNotExist_IsAGenuineFindingNotABlocker() [Fact] public void NoTypeEventsButAnUnresolvableSignature_IsUnverifiedNotFailed() { - // The real case: Create.DuctLogicalOrFilter(bool,bool,bool) exists with exactly + // The real case: Create.AreaLogicalOrFilter(bool,bool,bool) exists with exactly // the recorded signature, but returns Autodesk.Revit.DB.LogicalOrFilter, so no // type-level cause is ever recorded. The probe is the only way to tell. var leaf = new FakeTestInfo { Status = "Error", - Description = "BH.Revit.Engine.MechanicalPlumbing.Create. }", + Description = "BH.Example.Engine.Area.Create. }", Message = "Error: Returned null from json.", Information = [new FakeEventMessage { - Message = MethodCause.Replace("ApplyDuctInsulation", "DuctLogicalOrFilter") + Message = MethodCause.Replace("ApplyAreaOffset", "AreaLogicalOrFilter") .Replace(".Compute,", ".Create,") }] }; @@ -780,7 +780,7 @@ public void NoTypeEventsButAnUnresolvableSignature_IsUnverifiedNotFailed() Assert.Single(skips); Assert.Equal("Autodesk.Revit.DB.LogicalOrFilter", skips[0].Cause); // And the mangled description is replaced by the event's own naming. - Assert.Equal("BH.Revit.Engine.MechanicalPlumbing.Create.DuctLogicalOrFilter", skips[0].Description); + Assert.Equal("BH.Example.Engine.Area.Create.AreaLogicalOrFilter", skips[0].Description); } [Fact] @@ -789,11 +789,11 @@ public void MangledDescriptionOnARealFailure_IsRelabelledFromTheMethodEvent() var leaf = new FakeTestInfo { Status = "Error", - Description = "BH.Revit.Engine.MechanicalPlumbing.Create. }", + Description = "BH.Example.Engine.Area.Create. }", Message = "Error: Returned null from json.", Information = [new FakeEventMessage { - Message = MethodCause.Replace("ApplyDuctInsulation", "SomethingGenuine") + Message = MethodCause.Replace("ApplyAreaOffset", "SomethingGenuine") .Replace(".Compute,", ".Create,") }] }; @@ -804,7 +804,7 @@ public void MangledDescriptionOnARealFailure_IsRelabelledFromTheMethodEvent() var result = RunCommand.ExtractFilteredResult(outer, (_, _) => (true, AttributionBasis.NotRecorded), null, probeSignature: (_, _, _) => (null, ClassificationPath.NoOverloadFound, Array.Empty())); Assert.Equal(1, result.FailureCount); - Assert.Equal("BH.Revit.Engine.MechanicalPlumbing.Create.SomethingGenuine", + Assert.Equal("BH.Example.Engine.Area.Create.SomethingGenuine", result.Failures[0].Description); } @@ -879,7 +879,7 @@ public class ClassificationDiagnosticsTests "Type Autodesk.Revit.DB.Document, RevitAPI, Version=9.0.0.0 failed to deserialise."; private const string MethodCause = - "Method ApplyDuctInsulation from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Revit.Engine.MechanicalPlumbing.Compute, Revit_MechanicalPlumbing_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; + "Method ApplyAreaOffset from { \"_t\" : \"System.Type\", \"Name\" : \"BH.Example.Engine.Area.Compute, Example_Area_Engine_2022, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null\", \"_bhomVersion\" : \"9.2\" } failed to deserialise."; private static FakeTestResult Tree(string description, params string[] eventMessages) { @@ -928,14 +928,14 @@ public void NoMethodEvent_IsRecordedAsSuch_AndCountedReal() public void ProbeVerdict_AndDeclaringAssembly_AreCarriedIntoTheDiagnostic() { var diagnostics = new List(); - RunCommand.ExtractFilteredResult(Tree("BH.Revit.Engine.MechanicalPlumbing.Compute. }", MethodCause), + RunCommand.ExtractFilteredResult(Tree("BH.Example.Engine.Area.Compute. }", MethodCause), (_, _) => (true, AttributionBasis.NotRecorded), null, (_, _, _) => (null, ClassificationPath.DeclaringTypeNotLoaded, Array.Empty()), diagnostics); var only = Assert.Single(diagnostics); Assert.True(only.CountedAsReal); Assert.Equal(ClassificationPath.DeclaringTypeNotLoaded, only.Path); - Assert.Equal("BH.Revit.Engine.MechanicalPlumbing.Compute", only.DeclaringType); - Assert.Equal("Revit_MechanicalPlumbing_Engine_2022", only.DeclaringAssembly); + Assert.Equal("BH.Example.Engine.Area.Compute", only.DeclaringType); + Assert.Equal("Example_Area_Engine_2022", only.DeclaringAssembly); } [Fact] @@ -945,12 +945,12 @@ public void DeclaringAssemblyFromTheEvent_IsHandedToTheProbe() // the only source is the Method event's assembly-qualified "Name". string? seen = "not called"; RunCommand.ExtractFilteredResult( - Tree("BH.Revit.Engine.MechanicalPlumbing.Compute. }", MethodCause), + Tree("BH.Example.Engine.Area.Compute. }", MethodCause), (_, _) => (true, AttributionBasis.NotRecorded), null, (_, _, asm) => { seen = asm; return (null, ClassificationPath.DeclaringTypeNotLoaded, Array.Empty()); }, null); - Assert.Equal("Revit_MechanicalPlumbing_Engine_2022", seen); + Assert.Equal("Example_Area_Engine_2022", seen); } [Fact] @@ -995,7 +995,7 @@ public void ProbePaths_DistinguishNoOverloadFromResolvedSignature() [Fact] public void AssemblyParser_ReturnsNullWhenThereIsNothingToParse() { - Assert.Equal("Revit_MechanicalPlumbing_Engine_2022", RunCommand.ParseMethodEventAssembly(MethodCause)); + Assert.Equal("Example_Area_Engine_2022", RunCommand.ParseMethodEventAssembly(MethodCause)); Assert.Null(RunCommand.ParseMethodEventAssembly(RevitCause)); Assert.Null(RunCommand.ParseMethodEventAssembly("")); Assert.Null(RunCommand.ParseMethodEventAssembly("Method with no json payload")); @@ -1020,7 +1020,7 @@ public class ProbeDeclaringTypeTests public void TypeInTwoLoadedAssemblies_RecordsBothCandidates() { // The same assembly listed twice stands in for two repos declaring into one - // namespace, which is the real shape of CI_Toolkit#161. The probe result must be + // namespace, which is the real shape of the ambiguity. The probe result must be // unchanged and the ambiguity must be visible. var twice = new List { typeof(string).Assembly, typeof(string).Assembly }; @@ -1176,7 +1176,7 @@ public void OnlyErrorFailsTheJob() Assert.Equal(1, RunCommand.ExitCodeFor(VersioningStatus.Error)); } - // The two observed sandbox legs of the CI_Toolkit#159 A/B, which straddle the + // The two observed sandbox legs of the A/B, which straddle the // boundary: leg A 0 failures with 805 unverified, leg B 2 real failures. [Theory] [InlineData(0, 805, VersioningStatus.Warning, 0)] diff --git a/tools/VersioningRunner/src/VersioningRunner/Commands/RunCommand.cs b/tools/VersioningRunner/src/VersioningRunner/Commands/RunCommand.cs index 0a1a324..86279cb 100644 --- a/tools/VersioningRunner/src/VersioningRunner/Commands/RunCommand.cs +++ b/tools/VersioningRunner/src/VersioningRunner/Commands/RunCommand.cs @@ -237,7 +237,7 @@ public static int Execute( + "from a namespace prefix, which cannot separate this repository's types from those of repositories " + $"extending its namespace. Of those, {unresolvable} were separately unverifiable because the closure " + $"could not resolve a type they need and are reported under that cause; {unattributable} rest on the " - + "inference alone. None of them gate. See BHoM/internal-tickets#31."); + + "inference alone. None of them gate."); } } @@ -318,7 +318,7 @@ public static int Execute( if (ambiguous > 0) Console.Error.WriteLine( $"::warning title=Versioning::{ambiguous} finding(s) have a declaring type present in more than one loaded assembly, " + - "so their classification depended on assembly enumeration order. See CI_Toolkit#161."); + "so their classification depended on assembly enumeration order."); const int maxLogged = 50; foreach (var failure in result.Failures.Take(maxLogged)) @@ -411,9 +411,10 @@ internal static int ExitCodeFor(VersioningStatus status) // ProbeDeclaringType walks the loaded list and takes its verdict from the FIRST // assembly that yields the declaring type. Where two repos declare the same type, // whichever is enumerated first decides whether the finding reads as a genuine - // regression or as an infrastructure problem. That is CI_Toolkit#161's mechanism: - // BH.Revit.Engine.Core.Compute is defined by both Revit_Core_Engine and - // Revit_ModelQA_Engine, and 42 such type-level collisions exist across the fleet. + // regression or as an infrastructure problem. That is the ambiguity's mechanism: + // BH.Revit.Engine.Core.Compute is defined by Revit_Core_Engine and by a second + // engine assembly in another repository, and dozens of such type-level collisions + // exist across the fleet. // // Directory.GetFiles documents no ordering. Measured on windows-2025-vs2026 across // four cold-rebuild runs on separate runners, NTFS returned exactly @@ -659,9 +660,9 @@ private static void CollectLeafFailures( // prefix is genuinely all the evidence there is on that path. // // No namespace rule can be made correct here, so precision is not the fix: - // BH.oM.Structure.Elements is declared by both BHoM/BHoM and - // StructuralEngineering_Toolkit, and a string cannot say which of two repos - // sharing a namespace owns a type. Measured on BHoM/BHoM (run 33849699768, + // BH.oM.Structure.Elements is declared by BHoM/BHoM and by a downstream + // toolkit as well, and a string cannot say which of two repos sharing a + // namespace owns a type. Measured on BHoM/BHoM (run 33849699768, // commit d6db0a1): 145 of 145 findings arrived on this path and 0 by declaring // assembly, and 145 of 145 turned out to be another repository's or an artefact // of an incomplete closure. Re-running the same subject against a closure with @@ -671,7 +672,7 @@ private static void CollectLeafFailures( // Routed to the unverified bucket rather than dropped, because dropping would // lose a real regression that happens to sit on this path. DeriveStatus then // yields Warning and ExitCodeFor yields 0: still in the log, still in the - // artefact, no longer a gate. See BHoM/internal-tickets#31. + // artefact, no longer a gate. if (cause is null && attributedBy == AttributionBasis.NamespaceFallback) cause = "ownership inferred from a namespace prefix, because the dataset " + "record named no declaring assembly, so this finding may belong to " @@ -822,7 +823,7 @@ internal static HashSet ReadSubjectAssemblyListFrom(IEnumerable // provisioning soft-skips, the same shape recurs against RevitAPIUI: // measured on BHoM/Revit_Toolkit, 120 unreadable types led by // Revit_Core_Adapter_2022 failing to resolve RevitAPIUI. That one is - // tracked as issue #143 and is not fixed here. + // tracked separately and is not fixed here. try { if (type.Namespace is { Length: > 0 } ns) @@ -944,8 +945,8 @@ public static bool IsFromSubjectNamespace(string description, HashSet su return false; } - // "Method DuctLogicalOrFilter from { "_t" : "System.Type", "Name" : "BH.Revit.Engine - // .MechanicalPlumbing.Create, Revit_MechanicalPlumbing_Engine_2022, ..." ... } failed + // "Method SomeMethod from { "_t" : "System.Type", "Name" : "BH.Example.Engine + // .Area.Create, Example_Area_Engine_2022, ..." ... } failed // to deserialise." — the only event some failures carry. It names the method and its // declaring type where Description has been mangled to "...Create. }" by // Versioning_Toolkit's DescriptionFromJson, so it is the better label as well as the @@ -995,11 +996,11 @@ public static (string? DeclaringType, string? MethodName) ParseMethodEvent(strin // Some failures record no type-level cause at all, because the type that cannot be // resolved is in the method's signature rather than in the deserialised payload. - // Measured: BH.Revit.Engine.MechanicalPlumbing.Create.DuctLogicalOrFilter(bool,bool,bool) - // exists with exactly the recorded signature, so it is not a regression, but its return - // type is Autodesk.Revit.DB.LogicalOrFilter and the MethodInfo cannot be materialised - // without RevitAPI. Probe the signature and report the blocker only when it is - // positively observed and outside BHoM; anything else stays a real failure. + // The shape seen in practice is a Revit engine method that exists with exactly the + // recorded signature, so it is not a regression, but whose return type comes from + // Autodesk.Revit.DB, so the MethodInfo cannot be materialised without RevitAPI. + // Probe the signature and report the blocker only when it is positively observed + // and outside BHoM; anything else stays a real failure. // Resolves the declaring type, then either probes its signature or explains why it could // not be resolved. // @@ -1046,7 +1047,7 @@ internal static VersionConditionalState ClassifyVersionConditional(string? typeF // // The loop used to return on the first match, which made the classification depend on // assembly enumeration order wherever two repos declare into the same namespace - // (CI_Toolkit#161, measured on BH.Revit.Engine.Core). The probe result still comes from + // (measured on BH.Revit.Engine.Core). The probe result still comes from // the first match, so behaviour is unchanged; the candidate list is recorded so a // classification that could have gone either way is visible rather than silent. internal static (string? Cause, ClassificationPath Path, IReadOnlyList Candidates) ProbeDeclaringType( @@ -1253,7 +1254,8 @@ internal static (string? Cause, ClassificationPath Path) ClassifyDeclaringTypeFa // every downstream BHoM toolkit type also starts with BH.: measured on BHoM/BHoM run // 33849699768, all 145 findings named a BH. type and all 145 were closure artefacts. // Worse, the test discarded a cause the function had already computed correctly one line - // above, which is the same discard-at-the-handoff fault as BHoM/internal-tickets#31. + // above: the same discard-at-the-handoff fault as the namespace-fallback path, where a + // finding's real basis was computed and then thrown away before anything could report it. // // A genuine removal is still caught, and deliberately so: its namespace survives in the // loaded assembly the type was deleted from, so it takes the middle branch. Where a diff --git a/tools/VersioningRunner/src/VersioningRunner/Models/VersioningResult.cs b/tools/VersioningRunner/src/VersioningRunner/Models/VersioningResult.cs index 27e1e94..aa6dc67 100644 --- a/tools/VersioningRunner/src/VersioningRunner/Models/VersioningResult.cs +++ b/tools/VersioningRunner/src/VersioningRunner/Models/VersioningResult.cs @@ -139,7 +139,7 @@ public record FailureDiagnostic( string? DeclaringAssembly, int EventCount, // Every assembly in the loaded set that yielded the declaring type. More than one - // means the classification depended on enumeration order (see CI_Toolkit#161). + // means the classification depended on enumeration order. IReadOnlyList? DeclaringTypeCandidates = null, // Build configuration this run compiled, carried per row so a finding read on its // own is interpretable. Human-legible; it does not by itself explain a divergence. diff --git a/tools/VersioningRunner/src/VersioningRunner/Program.cs b/tools/VersioningRunner/src/VersioningRunner/Program.cs index 244f28f..1b3086c 100644 --- a/tools/VersioningRunner/src/VersioningRunner/Program.cs +++ b/tools/VersioningRunner/src/VersioningRunner/Program.cs @@ -60,7 +60,7 @@ // The distinction matters for most of the fleet, not an edge case: 75 of the 92 alpha and // beta repos are non-Revit and will legitimately produce an empty list. Treating empty as // "no evidence" would leave every versioning divergence on those repos unscored, and the -// gate in CI_Toolkit#173 could never be satisfied for them. +// scoring gate could never be satisfied for them. IReadOnlyCollection? versionConditional = null; if (vcFile is not null && File.Exists(vcFile)) { diff --git a/tools/VersioningRunner/src/VersioningRunner/VersioningRunner.csproj b/tools/VersioningRunner/src/VersioningRunner/VersioningRunner.csproj index 95d4ac4..2a18bba 100644 --- a/tools/VersioningRunner/src/VersioningRunner/VersioningRunner.csproj +++ b/tools/VersioningRunner/src/VersioningRunner/VersioningRunner.csproj @@ -10,21 +10,18 @@ a failure in one of them cannot be attributed to the repo that owns it, so it is silently dropped rather than reported. - Measured against the real 547-assembly closure with the Revit mocks present, - so this isolates the WPF cause from the RevitAPIUI one: + Measured on a real closure with the Revit mocks present, which isolates the + WPF cause from the RevitAPIUI one. On a plain net8.0 host, every Revit + tool's UI assembly reported most of its types as null and a further tranche + as unreadable; on net8.0-windows the same assemblies reported none of + either and gained namespaces. - subject net8.0 net8.0-windows - Revit_MechanicalPlumbing 115 null, 35 unreadable, 15 ns 0, 0, 17 ns - Revit_ElementRelationships 95 null, 20 unreadable, 4 ns 0, 0, 5 ns - Revit_Tagging 75 null, 10 unreadable, 7 ns 0, 0, 8 ns + The recovered namespaces are the UI ones, which were previously invisible + to attribution. - The recovered namespaces are the UI ones (e.g. - BH.Revit.UI.MechanicalPlumbing.Windows.Controls), which were previously - invisible to attribution. - - This moves toward BHoMBot rather than away from it: its VersioningProcess.exe - is .NET Framework, where these assemblies have always resolved. The plain - net8.0 host was the divergence. + This moves toward BHoMBot rather than away from it: it runs on .NET Framework, + where these assemblies have always resolved. The plain net8.0 host was the + divergence. --> net8.0-windows true