From 3050841860dc8f6f28ad38874b143f9a712d0e05 Mon Sep 17 00:00:00 2001 From: Thorsten Fuchs <76904008+ThorFuchs@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:16:56 +0100 Subject: [PATCH] fix(schema): locate resources beside native test bundles --- Documentation/Architecture/SchemaAndRegex.md | 11 +++++++++++ Scripts/qualify_distribution.py | 18 ++++++++++++++++-- Sources/JSONCLIEngine/CLIBuildIdentity.swift | 2 +- .../Document/JSONSchemaBuiltins.swift | 12 +++++++++++- Tools/DistributionAcceptance/Package.swift | 5 ++++- .../SchemaResourceHostTests.swift | 14 ++++++++++++++ 6 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 Tools/DistributionAcceptance/Tests/SchemaResourceHostTests/SchemaResourceHostTests.swift diff --git a/Documentation/Architecture/SchemaAndRegex.md b/Documentation/Architecture/SchemaAndRegex.md index b1b17ce..5539d24 100644 --- a/Documentation/Architecture/SchemaAndRegex.md +++ b/Documentation/Architecture/SchemaAndRegex.md @@ -17,6 +17,17 @@ owned quotas. Compilation is a separate public operation. JSONSchemaRegistry is an immutable bounded snapshot of explicitly supplied resource documents. Updating it produces a new snapshot. There is no automatic URLSession resolver or mutable global registry. A caller fetches resources through its own authorized application I/O before constructing the snapshot. Each compile consumes the selected snapshot within the compiler's own cumulative limits. +Packaged Draft 2020-12 resources retain their exact byte-size and SHA-256 checks. +On Apple platforms, a native SwiftPM test runner may be `Bundle.main` while the +schema code belongs to a nested `.xctest` bundle. After the existing app, +framework and executable locations, lookup checks the parent of that code-owner +`.xctest` for its sibling resource bundle. This is one fixed layout, not an +ancestor search or URL resolver. Lookup uses the same compilation meter; a +selected missing/corrupt payload throws an operational error without trying a +different payload. `Scripts/qualify_distribution.py` runs a public Swift Testing +consumer under the native backend in Debug and Release with resource overrides +removed, then preserves the standalone intact/missing/corrupt distribution cases. + Use JSONSchemaCompiler(options:).compile(document, registry:) throws -> CompiledJSONSchema and JSONSchemaValidator(options:).validate(instance, against:) throws -> JSONValidationResult. Compiled plans are checked Sendable and immutable; validators store configuration only. Plan IDs, resource IDs, keyword IDs and reference targets use bounded private indices. Avoid exposing upstream Schema, Context, OrderedDictionary or Regex types in signatures. Compilation validates dialect declaration, required vocabularies, meta-schema constraints, identifiers, anchors and resolvable resources. Support Draft 2020-12 as the only standard dialect. Custom meta-schema declarations using supported vocabularies can be resolved from the snapshot; unsupported required vocabularies fail. Unknown annotations follow dialect rules. Keep format annotation distinct from assertion; do not turn content metadata into unconditional decoding of instance text. diff --git a/Scripts/qualify_distribution.py b/Scripts/qualify_distribution.py index 87cfd3a..9bd4297 100644 --- a/Scripts/qualify_distribution.py +++ b/Scripts/qualify_distribution.py @@ -1,19 +1,22 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Public consumer plus installed-resource failures; touch only owned build output.""" -import argparse, hashlib, json, pathlib, subprocess, time +import argparse, hashlib, json, os, pathlib, re, subprocess, time ROOT = pathlib.Path(__file__).resolve().parents[1] def main(): p = argparse.ArgumentParser(); p.add_argument('--output', required=True); a = p.parse_args() output = ROOT / a.output; output.mkdir(parents=True, exist_ok=True) receipt = dict(status='failed', commands=[], resource_probes=3) + environment = os.environ.copy() + for key in ('PACKAGE_RESOURCE_BUNDLE_PATH', 'PACKAGE_RESOURCE_BUNDLE_URL'): + environment.pop(key, None) def save(): (output/'distribution.json').write_text(json.dumps(receipt,indent=2)+'\n') save() def run(name, command, expected=None): start = time.monotonic(); log = output / (name+'.log') with log.open('w') as stream: - result = subprocess.run(command, cwd=ROOT, stdout=stream, stderr=subprocess.STDOUT, timeout=1800) + result = subprocess.run(command, cwd=ROOT, env=environment, stdout=stream, stderr=subprocess.STDOUT, timeout=1800) text = log.read_text() receipt['commands'].append(dict(command=command, exit_code=result.returncode, seconds=time.monotonic()-start, log=log.name, sha256=hashlib.sha256(log.read_bytes()).hexdigest())) @@ -21,6 +24,17 @@ def run(name, command, expected=None): assert result.returncode == 0 and (expected is None or expected in text), name return text.strip() package = 'Tools/DistributionAcceptance' + # The native backend loads tests from a nested .xctest image under a helper. + # A standalone executable does not exercise its sibling-bundle discovery. + for configuration in ('debug', 'release'): + text = run('native-test-host-' + configuration, + ['swift', 'test', '-c', configuration, '--build-system', 'native', + '--package-path', package, '--scratch-path', str(output / 'native-test-host')], + 'DISTRIBUTION native_test_host nine_offline_resources exact_verdicts') + assert re.findall(r'Test run with (\d+) tests? in[^\n]*passed', text) == ['1'], 'Native host discovery changed' + receipt['native_test_host'] = dict(configurations=['debug', 'release'], functions=1, + backend='native', resource_overrides_used=False) + save() run('build', ['swift','build','-c','release','--package-path',package]) directory = pathlib.Path(run('bin-path', ['swift','build','-c','release','--package-path',package,'--show-bin-path'])) executable = directory / 'DistributionAcceptance' diff --git a/Sources/JSONCLIEngine/CLIBuildIdentity.swift b/Sources/JSONCLIEngine/CLIBuildIdentity.swift index a659e0c..065668b 100644 --- a/Sources/JSONCLIEngine/CLIBuildIdentity.swift +++ b/Sources/JSONCLIEngine/CLIBuildIdentity.swift @@ -2,6 +2,6 @@ // Generated by Scripts/generate_cli_identity.py; excludes this file. // SHA-256 of the ordered Package.swift + Sources content manifest, not a Git commit. package enum CLIBuildIdentity { - package static let revision = "sha256:138ae7e8bc2f4392db7aafaa000f60db5915658a1b34a55f12a9c27958da1312" + package static let revision = "sha256:52ec97c92df8f4518ecfbf604b895124915205e9ce6c509b05fa31f4aed62452" package static let kind = "source-sha256" } diff --git a/Sources/JSONSchema/Document/JSONSchemaBuiltins.swift b/Sources/JSONSchema/Document/JSONSchemaBuiltins.swift index 4e1f80c..79a2d79 100644 --- a/Sources/JSONSchema/Document/JSONSchemaBuiltins.swift +++ b/Sources/JSONSchema/Document/JSONSchemaBuiltins.swift @@ -48,12 +48,22 @@ extension JSONSchemaRegistry { #endif if let directory = Bundle.main.resourceURL { candidates.append(directory) } #if canImport(ObjectiveC) - if let directory = Bundle(for: JSONSchemaBundleAnchor.self).resourceURL { candidates.append(directory) } + let codeBundle = Bundle(for: JSONSchemaBundleAnchor.self) + if let directory = codeBundle.resourceURL { candidates.append(directory) } #endif candidates.append(Bundle.main.bundleURL) if let command = CommandLine.arguments.first { candidates.append(URL(fileURLWithPath: command).deletingLastPathComponent()) } + #if canImport(ObjectiveC) + // Native SwiftPM uses a helper as Bundle.main. The code-owner .xctest + // bundle has sibling resources. Search that fixed layout last; existing + // locations and corruption pins remain authoritative. + let ownerBundle = codeBundle.bundleURL + if ownerBundle.pathExtension == "xctest" { + candidates.append(ownerBundle.deletingLastPathComponent()) + } + #endif let root = try builtinResourceRoot(in: candidates, meter: &meter) #endif for (path, identifier, size, expectedHash) in files { diff --git a/Tools/DistributionAcceptance/Package.swift b/Tools/DistributionAcceptance/Package.swift index 6881b01..299d85d 100644 --- a/Tools/DistributionAcceptance/Package.swift +++ b/Tools/DistributionAcceptance/Package.swift @@ -8,5 +8,8 @@ let package = Package( .product(name: "JSON", package: "Swift-JSON"), .product(name: "JSONSchema", package: "Swift-JSON"), .product(name: "JSONStreaming", package: "Swift-JSON"), - .product(name: "JSONPatch", package: "Swift-JSON")])], + .product(name: "JSONPatch", package: "Swift-JSON")]), + .testTarget(name: "SchemaResourceHostTests", dependencies: [ + .product(name: "JSON", package: "Swift-JSON"), + .product(name: "JSONSchema", package: "Swift-JSON")])], swiftLanguageModes: [.v6]) diff --git a/Tools/DistributionAcceptance/Tests/SchemaResourceHostTests/SchemaResourceHostTests.swift b/Tools/DistributionAcceptance/Tests/SchemaResourceHostTests/SchemaResourceHostTests.swift new file mode 100644 index 0000000..31acf03 --- /dev/null +++ b/Tools/DistributionAcceptance/Tests/SchemaResourceHostTests/SchemaResourceHostTests.swift @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +import Testing +import JSON +import JSONSchema + +@Test func nativeTestHostLoadsPackagedSchemasWithoutOverrides() throws { + let registry = try JSONSchemaRegistry.draft2020_12() + #expect(registry.documents.count == 9) + let raw = try JSONParser().parse(#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","minimum":9007199254740993}"#) + let plan = try JSONSchemaCompiler().compile(JSONSchemaDocument(raw), registry: registry) + #expect(try JSONSchemaValidator().validate(JSONParser().parse("9007199254740993"), against: plan).isValid) + #expect(try !JSONSchemaValidator().validate(JSONParser().parse("9007199254740992"), against: plan).isValid) + print("DISTRIBUTION native_test_host nine_offline_resources exact_verdicts") +}