@voxgig/sdkgen 4.5.3 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/action/check.js +15 -0
- package/dist/action/check.js.map +1 -1
- package/dist/cmp/AgentGuide.js +1 -1
- package/dist/cmp/AgentGuide.js.map +1 -1
- package/dist/cmp/AgentGuideContent.d.ts +1 -1
- package/dist/cmp/AgentGuideContent.js +8 -2
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +17 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureDocs.d.ts +1 -1
- package/dist/cmp/FeatureDocs.js +6 -1
- package/dist/cmp/FeatureDocs.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -1
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.js +1 -1
- package/dist/cmp/ReadmeFeatures.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.js +1 -1
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
- package/dist/helpers/applicability.d.ts +6 -0
- package/dist/helpers/applicability.js +103 -0
- package/dist/helpers/applicability.js.map +1 -0
- package/dist/helpers/collectDeps.js +4 -2
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +2 -1
- package/dist/helpers/serverVars.js +17 -0
- package/dist/helpers/serverVars.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +7 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +39 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aon +1 -0
- package/project/.sdk/model/feature/secrets.aon +118 -0
- package/project/.sdk/model/target/ts.aon +8 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
- package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
- package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
- package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
- package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
- package/project/.sdk/tm/c/utility/make_options.c +22 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
- package/project/.sdk/tm/go/core/context.go +1 -1
- package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
- package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
- package/project/.sdk/tm/go/feature/test_feature.go +12 -5
- package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
- package/project/.sdk/tm/go/test/runner_test.go +46 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
- package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
- package/project/.sdk/tm/go/utility/fetcher.go +2 -2
- package/project/.sdk/tm/go/utility/make_options.go +24 -5
- package/project/.sdk/tm/go/utility/make_point.go +1 -1
- package/project/.sdk/tm/go/utility/make_spec.go +17 -1
- package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
- package/project/.sdk/tm/go/utility/transform_request.go +29 -1
- package/project/.sdk/tm/go/utility/transform_response.go +20 -1
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
- package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
- package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
- package/project/.sdk/tm/py/test/runner.py +32 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
- package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
- package/project/.sdk/tm/scala/utility/Make.scala +17 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
- package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
- package/project/.sdk/tm/ts/test/omni.ts +86 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
- package/project/.sdk/tm/ts/test/utility.ts +122 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
- package/project/.sdk/tm/zig/core/utility.zig +23 -0
- package/project/sdkgen-package.json +2 -1
- package/src/action/check.ts +18 -0
- package/src/cmp/AgentGuide.ts +1 -1
- package/src/cmp/AgentGuideContent.ts +9 -2
- package/src/cmp/Feature.ts +19 -0
- package/src/cmp/FeatureDocs.ts +7 -1
- package/src/cmp/ReadmeExplanation.ts +4 -1
- package/src/cmp/ReadmeFeatures.ts +1 -1
- package/src/cmp/ReadmeRefFeatures.ts +1 -1
- package/src/helpers/applicability.ts +123 -0
- package/src/helpers/collectDeps.ts +5 -1
- package/src/helpers/serverVars.ts +19 -0
- package/src/sdkgen.ts +9 -1
- package/src/utility.ts +10 -1
- package/project/.sdk/tm/ts/test/runner.ts +0 -425
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
// VENDORED: @voxgig/omni 0.1.2 (typescript/src/Runner.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/omni @ bc9535d655564c0833f6eff003b0b13dad8b350f
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
// Omni: the shared multi-language test runner.
|
|
5
|
+
//
|
|
6
|
+
// A test spec is plain JSON. The same spec file drives the same tests in
|
|
7
|
+
// every language that ships an omni port, so behaviour is defined once and
|
|
8
|
+
// verified everywhere.
|
|
9
|
+
//
|
|
10
|
+
// This file is CANONICAL. Every other port is a translation of it.
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
EXISTSMARK,
|
|
16
|
+
Json,
|
|
17
|
+
NULLMARK,
|
|
18
|
+
UNDEFMARK,
|
|
19
|
+
clone,
|
|
20
|
+
deepequal,
|
|
21
|
+
getpath,
|
|
22
|
+
islist,
|
|
23
|
+
ismap,
|
|
24
|
+
isnode,
|
|
25
|
+
pathify,
|
|
26
|
+
stringify,
|
|
27
|
+
walk,
|
|
28
|
+
} from './Util'
|
|
29
|
+
|
|
30
|
+
// A test subject: the function under test.
|
|
31
|
+
export type Subject = (...args: Json[]) => any
|
|
32
|
+
|
|
33
|
+
// Run-time options for a set of test entries.
|
|
34
|
+
export type Flags = {
|
|
35
|
+
// Convert JSON nulls to the NULLMARK sentinel (default: true).
|
|
36
|
+
null?: boolean
|
|
37
|
+
// Label used in failure messages (default: the runner name).
|
|
38
|
+
name?: string
|
|
39
|
+
[flag: string]: any
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The host of the system under test. Every hook is optional: a spec that
|
|
43
|
+
// resolves its subjects explicitly needs no provider at all.
|
|
44
|
+
export type Provider = {
|
|
45
|
+
// Resolve a test subject by name.
|
|
46
|
+
subject?: (name: string) => Subject | undefined | null
|
|
47
|
+
// Build a sub-provider from a spec DEF.client entry's options.
|
|
48
|
+
client?: (options: Json) => Provider | Promise<Provider>
|
|
49
|
+
// Wrap a map argument as a call context before it is passed in.
|
|
50
|
+
contextify?: (val: Json) => Json
|
|
51
|
+
// Resolve references in client options against the runner store.
|
|
52
|
+
inject?: (options: Json, store: Json) => Json
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type RunSet = (testspec: Json, testsubject?: Subject) => Promise<void>
|
|
56
|
+
|
|
57
|
+
export type RunSetFlags = (
|
|
58
|
+
testspec: Json,
|
|
59
|
+
flags: Flags,
|
|
60
|
+
testsubject?: Subject,
|
|
61
|
+
) => Promise<void>
|
|
62
|
+
|
|
63
|
+
export type RunPack = {
|
|
64
|
+
// The resolved spec (the named section, or the whole file).
|
|
65
|
+
spec: Json
|
|
66
|
+
// Run one set of test entries.
|
|
67
|
+
runset: RunSet
|
|
68
|
+
// Run one set of test entries with flags.
|
|
69
|
+
runsetflags: RunSetFlags
|
|
70
|
+
// The default subject, if the provider could resolve one.
|
|
71
|
+
subject?: Subject
|
|
72
|
+
// The root provider.
|
|
73
|
+
client?: Provider
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type Runner = (name?: string, store?: Json) => Promise<RunPack>
|
|
77
|
+
|
|
78
|
+
// The newest spec format version this runner understands. A spec with no
|
|
79
|
+
// OMNI block is version 0: the original, lenient format, frozen forever.
|
|
80
|
+
// Version 1 turns on strict entry validation (see checkentry).
|
|
81
|
+
export const SPECVERSION = 1
|
|
82
|
+
|
|
83
|
+
// Capability strings this runner supports beyond the version baseline. A
|
|
84
|
+
// spec's OMNI.requires list is checked against this: an unknown capability
|
|
85
|
+
// refuses the spec loudly at load time, instead of a lagging port silently
|
|
86
|
+
// mis-running it. (Empty today; future format features mint a string here.)
|
|
87
|
+
export const CAPABILITIES: string[] = []
|
|
88
|
+
|
|
89
|
+
// The complete set of fields an entry may carry. Under version 1 anything
|
|
90
|
+
// else is an error: an unrecognised key is almost always a typo'd
|
|
91
|
+
// assertion, and a typo'd assertion is a test that silently stopped
|
|
92
|
+
// testing.
|
|
93
|
+
const ENTRYFIELDS = ['in', 'args', 'ctx', 'out', 'err', 'match', 'client', 'id', 'doc']
|
|
94
|
+
|
|
95
|
+
// A test failure (or a malformed spec). Distinct from errors thrown by the
|
|
96
|
+
// subject under test, which are candidates for an `err` expectation.
|
|
97
|
+
export class OmniError extends Error {
|
|
98
|
+
entry?: Json
|
|
99
|
+
constructor(message: string, entry?: Json) {
|
|
100
|
+
super(message)
|
|
101
|
+
this.name = 'OmniError'
|
|
102
|
+
this.entry = entry
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Load a spec: either a path to a JSON file, or an already-parsed object.
|
|
107
|
+
function loadspec(specref: string | Json): Json {
|
|
108
|
+
if ('string' === typeof specref) {
|
|
109
|
+
return JSON.parse(readFileSync(specref, 'utf8'))
|
|
110
|
+
}
|
|
111
|
+
return specref
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Read the spec's format version from its optional top-level OMNI block,
|
|
115
|
+
// and refuse a spec this runner cannot faithfully run: a version newer
|
|
116
|
+
// than SPECVERSION, or a required capability not in CAPABILITIES.
|
|
117
|
+
function resolveversion(alltests: Json): number {
|
|
118
|
+
const meta = ismap(alltests) ? alltests.OMNI : undefined
|
|
119
|
+
|
|
120
|
+
if (undefined === meta) {
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!ismap(meta) || 'number' !== typeof meta.version || 0 !== meta.version % 1) {
|
|
125
|
+
throw new OmniError('omni: malformed OMNI version block')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (meta.version < 0 || SPECVERSION < meta.version) {
|
|
129
|
+
throw new OmniError('omni: unsupported spec version: ' + meta.version)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const requires = meta.requires
|
|
133
|
+
if (undefined !== requires) {
|
|
134
|
+
if (!islist(requires)) {
|
|
135
|
+
throw new OmniError('omni: malformed OMNI requires list')
|
|
136
|
+
}
|
|
137
|
+
for (const cap of requires) {
|
|
138
|
+
if ('string' !== typeof cap || !CAPABILITIES.includes(cap)) {
|
|
139
|
+
throw new OmniError('omni: spec requires unsupported capability: ' + stringify(cap))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return meta.version
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Strict entry validation, applied when the spec declares version 1 or
|
|
148
|
+
// later. The lenient format converts each of these mistakes into a silent
|
|
149
|
+
// pass or a dead field; here they fail with the entry named.
|
|
150
|
+
function checkentry(flags: Flags, index: number, entry: Json): void {
|
|
151
|
+
if (!ismap(entry)) {
|
|
152
|
+
throw fail(flags, index, entry, 'entry is not a map')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const key of Object.keys(entry)) {
|
|
156
|
+
if (!ENTRYFIELDS.includes(key)) {
|
|
157
|
+
throw fail(flags, index, entry, 'unknown entry field: ' + key)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let argsources = 0
|
|
162
|
+
for (const key of ['in', 'args', 'ctx']) {
|
|
163
|
+
if (undefined !== entry[key]) {
|
|
164
|
+
argsources++
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (1 < argsources) {
|
|
168
|
+
throw fail(flags, index, entry, 'entry has more than one of in, args, ctx')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (null != entry.err && undefined !== entry.out) {
|
|
172
|
+
throw fail(flags, index, entry, 'entry has both err and out')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (undefined !== entry.id && 'string' !== typeof entry.id) {
|
|
176
|
+
throw fail(flags, index, entry, 'entry id is not a string')
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Validate a version-1 group up front, against the AUTHORED entries -
|
|
181
|
+
// null-normalisation would otherwise rewrite an authored null (e.g.
|
|
182
|
+
// id: null) into a sentinel string and hide it from validation. A
|
|
183
|
+
// malformed spec is a spec error, not a test result, so it fails before
|
|
184
|
+
// any subject runs.
|
|
185
|
+
function checkset(flags: Flags, testspec: Json, normalset: Json[]): void {
|
|
186
|
+
const origset =
|
|
187
|
+
ismap(testspec) && islist(testspec.set) ? testspec.set : normalset
|
|
188
|
+
|
|
189
|
+
if (0 === origset.length && true !== (ismap(testspec) ? testspec.empty : undefined)) {
|
|
190
|
+
throw new OmniError('omni: empty test set: ' + flags.name)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (let index = 0; index < origset.length; index++) {
|
|
194
|
+
checkentry(flags, index, origset[index])
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Find the named section of a spec: `primary.<name>`, then `<name>`, then
|
|
199
|
+
// the whole spec.
|
|
200
|
+
function resolvespec(name: string | undefined, alltests: Json): Json {
|
|
201
|
+
if (null == name) {
|
|
202
|
+
return alltests
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const primary = alltests.primary
|
|
206
|
+
if (ismap(primary) && null != primary[name]) {
|
|
207
|
+
return primary[name]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (ismap(alltests) && null != alltests[name]) {
|
|
211
|
+
return alltests[name]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return alltests
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Build the named clients declared by the spec's DEF.client block.
|
|
218
|
+
async function resolveclients(
|
|
219
|
+
provider: Provider,
|
|
220
|
+
spec: Json,
|
|
221
|
+
store: Json,
|
|
222
|
+
): Promise<Record<string, Provider>> {
|
|
223
|
+
const clients: Record<string, Provider> = {}
|
|
224
|
+
|
|
225
|
+
const defclient = ismap(spec) && ismap(spec.DEF) ? spec.DEF.client : undefined
|
|
226
|
+
if (!ismap(defclient)) {
|
|
227
|
+
return clients
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// A spec may define clients that a given test run never references.
|
|
231
|
+
if (null == provider.client) {
|
|
232
|
+
return clients
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const clientname of Object.keys(defclient)) {
|
|
236
|
+
const cdef = defclient[clientname]
|
|
237
|
+
const copts = clone((ismap(cdef) && ismap(cdef.test) ? cdef.test.options : undefined) || {})
|
|
238
|
+
|
|
239
|
+
if (ismap(store) && null != provider.inject) {
|
|
240
|
+
provider.inject(copts, store)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
clients[clientname] = await provider.client(copts)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return clients
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function resolvesubject(name: string | undefined, provider: Provider): Subject | undefined {
|
|
250
|
+
if (null == name || null == provider.subject) {
|
|
251
|
+
return undefined
|
|
252
|
+
}
|
|
253
|
+
return provider.subject(name) || undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function resolveflags(flags?: Flags): Flags {
|
|
257
|
+
const out: Flags = null == flags ? {} : { ...flags }
|
|
258
|
+
out.null = null == out.null ? true : !!out.null
|
|
259
|
+
return out
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// An entry with no `out` expects a null (or absent) result.
|
|
263
|
+
function resolveentry(entry: Json, flags: Flags): Json {
|
|
264
|
+
if (null == entry.out && flags.null) {
|
|
265
|
+
entry.out = NULLMARK
|
|
266
|
+
}
|
|
267
|
+
return entry
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function resolvetestpack(
|
|
271
|
+
name: string | undefined,
|
|
272
|
+
entry: Json,
|
|
273
|
+
subject: Subject,
|
|
274
|
+
provider: Provider,
|
|
275
|
+
clients: Record<string, Provider>,
|
|
276
|
+
) {
|
|
277
|
+
const testpack = { client: provider, subject }
|
|
278
|
+
|
|
279
|
+
if (null != entry.client) {
|
|
280
|
+
const client = clients[entry.client]
|
|
281
|
+
if (null == client) {
|
|
282
|
+
throw new OmniError('omni: unknown client: ' + entry.client, entry)
|
|
283
|
+
}
|
|
284
|
+
testpack.client = client
|
|
285
|
+
testpack.subject = resolvesubject(name, client) || subject
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return testpack
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Build the argument list for one entry: `ctx` (single context argument),
|
|
292
|
+
// `args` (explicit list), or `in` (single value).
|
|
293
|
+
function resolveargs(entry: Json, testpack: { client: Provider }, provider: Provider): Json[] {
|
|
294
|
+
let args: Json[]
|
|
295
|
+
|
|
296
|
+
if (undefined !== entry.ctx) {
|
|
297
|
+
args = [entry.ctx]
|
|
298
|
+
} else if (undefined !== entry.args) {
|
|
299
|
+
args = entry.args
|
|
300
|
+
} else {
|
|
301
|
+
args = [clone(entry.in)]
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (undefined !== entry.ctx || undefined !== entry.args) {
|
|
305
|
+
let first = args[0]
|
|
306
|
+
if (ismap(first)) {
|
|
307
|
+
first = clone(first)
|
|
308
|
+
if (null != provider.contextify) {
|
|
309
|
+
first = provider.contextify(first)
|
|
310
|
+
}
|
|
311
|
+
args[0] = first
|
|
312
|
+
entry.ctx = first
|
|
313
|
+
if (ismap(first)) {
|
|
314
|
+
first.client = testpack.client
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return args
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Normalise a value for comparison: JSON nulls (and absent values) become
|
|
323
|
+
// the NULLMARK sentinel, errors become {name,message} maps. Always returns
|
|
324
|
+
// a fresh copy, so a spec can be run repeatedly.
|
|
325
|
+
function fixjson(val: Json, flags?: Flags): Json {
|
|
326
|
+
const donull = null == flags || null == flags.null ? true : !!flags.null
|
|
327
|
+
return fixjsonval(val, donull)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function fixjsonval(val: Json, donull: boolean): Json {
|
|
331
|
+
if (null == val) {
|
|
332
|
+
return donull ? NULLMARK : val
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (val instanceof Error) {
|
|
336
|
+
return errify(val)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (islist(val)) {
|
|
340
|
+
return val.map((entry: Json) => fixjsonval(entry, donull))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (ismap(val)) {
|
|
344
|
+
const out: Record<string, Json> = {}
|
|
345
|
+
for (const key of Object.keys(val)) {
|
|
346
|
+
out[key] = fixjsonval(val[key], donull)
|
|
347
|
+
}
|
|
348
|
+
return out
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return val
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The JSON form of an error: always at least {name,message}.
|
|
355
|
+
// PATCH (solardemo prototype, pending upstream fix), here and in
|
|
356
|
+
// errmessage below: handle error-shaped MAPS. The shared corpus throws
|
|
357
|
+
// plain objects in some ports (this SDK's makeError rethrows the
|
|
358
|
+
// fixture's ctrl.err verbatim), and the struct repos' original runner
|
|
359
|
+
// matched err.message regardless of the thrown value's class. Upstream
|
|
360
|
+
// collapses any non-Error to String(err) — '[object Object]' — which
|
|
361
|
+
// fails both the err check and every match.err.* leaf.
|
|
362
|
+
function errify(err: any): Json {
|
|
363
|
+
if (err instanceof Error) {
|
|
364
|
+
return { ...err, name: err.name, message: err.message }
|
|
365
|
+
}
|
|
366
|
+
if (null != err && 'object' === typeof err) {
|
|
367
|
+
return { name: 'Error', ...err }
|
|
368
|
+
}
|
|
369
|
+
return { name: 'Error', message: String(err) }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function errmessage(err: any): string {
|
|
373
|
+
return err instanceof Error ? err.message
|
|
374
|
+
: null != err && 'string' === typeof err.message ? err.message
|
|
375
|
+
: String(err)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// The label of one entry, for failure messages.
|
|
379
|
+
function entryref(flags: Flags, index: number, entry: Json): string {
|
|
380
|
+
const label = flags.name || 'set'
|
|
381
|
+
const id = null != entry && null != entry.id ? ' (' + entry.id + ')' : ''
|
|
382
|
+
return label + '[' + index + ']' + id
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function fail(
|
|
386
|
+
flags: Flags,
|
|
387
|
+
index: number,
|
|
388
|
+
entry: Json,
|
|
389
|
+
reason: string,
|
|
390
|
+
expected?: string,
|
|
391
|
+
actual?: string,
|
|
392
|
+
): OmniError {
|
|
393
|
+
let msg = 'omni: ' + entryref(flags, index, entry) + ': ' + reason
|
|
394
|
+
if (undefined !== expected) {
|
|
395
|
+
msg += '\n expected: ' + expected
|
|
396
|
+
}
|
|
397
|
+
if (undefined !== actual) {
|
|
398
|
+
msg += '\n actual: ' + actual
|
|
399
|
+
}
|
|
400
|
+
msg += '\n entry: ' + stringify(entrysummary(entry))
|
|
401
|
+
return new OmniError(msg, entry)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// The spec-defined part of an entry (drop runner bookkeeping).
|
|
405
|
+
function entrysummary(entry: Json): Json {
|
|
406
|
+
if (!ismap(entry)) {
|
|
407
|
+
return entry
|
|
408
|
+
}
|
|
409
|
+
const out: Record<string, Json> = {}
|
|
410
|
+
for (const key of Object.keys(entry)) {
|
|
411
|
+
if ('res' !== key && 'thrown' !== key && 'ctx' !== key) {
|
|
412
|
+
out[key] = entry[key]
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return out
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function checkresult(flags: Flags, index: number, entry: Json, args: Json[], res: Json) {
|
|
419
|
+
let matched = false
|
|
420
|
+
|
|
421
|
+
if (null != entry.err) {
|
|
422
|
+
throw fail(
|
|
423
|
+
flags,
|
|
424
|
+
index,
|
|
425
|
+
entry,
|
|
426
|
+
'expected error did not occur',
|
|
427
|
+
stringify(entry.err),
|
|
428
|
+
stringify(res),
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (null != entry.match) {
|
|
433
|
+
match(flags, index, entry, entry.match, {
|
|
434
|
+
in: entry.in,
|
|
435
|
+
args,
|
|
436
|
+
out: entry.res,
|
|
437
|
+
ctx: entry.ctx,
|
|
438
|
+
})
|
|
439
|
+
matched = true
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const out = entry.out
|
|
443
|
+
|
|
444
|
+
if (out === res) {
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// NOTE: a match with no explicit out is a complete check on its own.
|
|
449
|
+
if (matched && (NULLMARK === out || null == out)) {
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!deepequal(res, out)) {
|
|
454
|
+
throw fail(flags, index, entry, 'result mismatch', stringify(out), stringify(res))
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function handleerror(flags: Flags, index: number, entry: Json, err: any) {
|
|
459
|
+
entry.thrown = err
|
|
460
|
+
|
|
461
|
+
const entryerr = entry.err
|
|
462
|
+
|
|
463
|
+
if (null != entryerr) {
|
|
464
|
+
if (true === entryerr || matchval(entryerr, errmessage(err))) {
|
|
465
|
+
if (null != entry.match) {
|
|
466
|
+
match(flags, index, entry, entry.match, {
|
|
467
|
+
in: entry.in,
|
|
468
|
+
out: entry.res,
|
|
469
|
+
ctx: entry.ctx,
|
|
470
|
+
err: errify(err),
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
return
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
throw fail(flags, index, entry, 'error mismatch', stringify(entryerr), errmessage(err))
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
throw fail(flags, index, entry, 'unexpected error', undefined, errmessage(err))
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check that every leaf of `check` is present, and matches, in `base`.
|
|
483
|
+
function match(flags: Flags, index: number, entry: Json, check: Json, base: Json) {
|
|
484
|
+
// PATCH (solardemo prototype, pending upstream fix): read the base
|
|
485
|
+
// directly instead of cloning it. The clone gained nothing (the walk
|
|
486
|
+
// below only READS via getpath) and blew the stack on cyclic bases —
|
|
487
|
+
// which every corpus ctx entry produces, because a live Context reaches
|
|
488
|
+
// its client and the client's root context reaches the client again.
|
|
489
|
+
const cbase = base
|
|
490
|
+
|
|
491
|
+
const at = (path: Json[]) => (0 === path.length ? '<root>' : pathify(path))
|
|
492
|
+
|
|
493
|
+
walk(clone(check), (_key, val, _parent, path) => {
|
|
494
|
+
// An empty container in the check is a structural placeholder: it has
|
|
495
|
+
// no leaves to check, so it matches whatever is at that path. This is
|
|
496
|
+
// deliberate - voxgig/struct's corpus relies on it (struct-compat), and
|
|
497
|
+
// an empty sub-pattern matching anything is the usual partial-match
|
|
498
|
+
// convention. The leaf checks below are where the strictness lives.
|
|
499
|
+
if (!isnode(val)) {
|
|
500
|
+
const baseval = getpath(cbase, path)
|
|
501
|
+
|
|
502
|
+
// The sentinels are tested BEFORE the identity check below. Otherwise
|
|
503
|
+
// a subject returning the literal string "__UNDEF__" satisfies an
|
|
504
|
+
// assertion that the key is absent - two mutually exclusive states
|
|
505
|
+
// passing one check. A sentinel that accepts its own literal is not a
|
|
506
|
+
// sentinel. (NULLMARK still accepts NULLMARK: under the default null
|
|
507
|
+
// flag a real null has already been normalised to it, so the two are
|
|
508
|
+
// genuinely indistinguishable here - that one needs a raw-value
|
|
509
|
+
// escape, not an ordering change.)
|
|
510
|
+
|
|
511
|
+
// Explicitly absent: satisfied only by a genuinely missing key, never
|
|
512
|
+
// by a present null (the distinction the sentinels exist to keep).
|
|
513
|
+
if (UNDEFMARK === val) {
|
|
514
|
+
if (undefined === baseval) {
|
|
515
|
+
return val
|
|
516
|
+
}
|
|
517
|
+
throw fail(flags, index, entry, 'expected absent at ' + at(path), 'absent', stringify(baseval))
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Explicitly null: satisfied only by a present null.
|
|
521
|
+
if (NULLMARK === val) {
|
|
522
|
+
if (null === baseval || NULLMARK === baseval) {
|
|
523
|
+
return val
|
|
524
|
+
}
|
|
525
|
+
throw fail(flags, index, entry, 'expected null at ' + at(path), 'null', stringify(baseval))
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Explicitly present: any present value, including null.
|
|
529
|
+
if (EXISTSMARK === val) {
|
|
530
|
+
if (undefined !== baseval) {
|
|
531
|
+
return val
|
|
532
|
+
}
|
|
533
|
+
throw fail(flags, index, entry, 'expected present at ' + at(path), 'present', 'absent')
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Identical values match. This sits below the sentinel branches on
|
|
537
|
+
// purpose - see the note above.
|
|
538
|
+
if (baseval === val) {
|
|
539
|
+
return val
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// A concrete expectation never matches a missing key - a match leaf
|
|
543
|
+
// against an absent value must fail, not substring-match "undefined".
|
|
544
|
+
if (undefined === baseval) {
|
|
545
|
+
throw fail(flags, index, entry, 'match failed at ' + at(path), stringify(val), 'absent')
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!matchval(val, baseval)) {
|
|
549
|
+
throw fail(flags, index, entry, 'match failed at ' + at(path), stringify(val), stringify(baseval))
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return val
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
// Match one leaf. Strings are matched as /regex/ or as a case-insensitive
|
|
559
|
+
// substring, which is what makes error-message expectations portable.
|
|
560
|
+
function matchval(check: Json, base: Json): boolean {
|
|
561
|
+
if (check === base) {
|
|
562
|
+
return true
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let want = check
|
|
566
|
+
if (UNDEFMARK === want || NULLMARK === want) {
|
|
567
|
+
want = null
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (null == want) {
|
|
571
|
+
return null == base || NULLMARK === base
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if ('string' === typeof want) {
|
|
575
|
+
// An empty want is not a wildcard: the empty string is a substring of
|
|
576
|
+
// everything, so `match:{out:""}` (or `err:""`) would accept any value.
|
|
577
|
+
if ('' === want) {
|
|
578
|
+
return '' === base
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const basestr = stringify(base)
|
|
582
|
+
|
|
583
|
+
const rem = want.match(/^\/(.+)\/$/)
|
|
584
|
+
if (rem) {
|
|
585
|
+
return new RegExp(rem[1]).test(basestr)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return basestr.toLowerCase().includes(want.toLowerCase())
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if ('function' === typeof want) {
|
|
592
|
+
return true
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return deepequal(want, base)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Convert NULLMARK sentinels back into real nulls. Use as a walk callback
|
|
599
|
+
// when a spec value must reach the subject as an actual null.
|
|
600
|
+
function nullmodifier(val: Json, key: any, parent: Json) {
|
|
601
|
+
if (NULLMARK === val) {
|
|
602
|
+
parent[key] = null
|
|
603
|
+
} else if ('string' === typeof val) {
|
|
604
|
+
parent[key] = val.split(NULLMARK).join('null')
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Make a runner for a spec file (or spec object) and a provider.
|
|
609
|
+
async function makeRunner(specref: string | Json, provider?: Provider): Promise<Runner> {
|
|
610
|
+
const alltests = loadspec(specref)
|
|
611
|
+
const specversion = resolveversion(alltests)
|
|
612
|
+
const useprovider: Provider = provider || {}
|
|
613
|
+
|
|
614
|
+
return async function runner(name?: string, store?: Json): Promise<RunPack> {
|
|
615
|
+
const spec = resolvespec(name, alltests)
|
|
616
|
+
const clients = await resolveclients(useprovider, spec, store || {})
|
|
617
|
+
const defsubject = resolvesubject(name, useprovider)
|
|
618
|
+
|
|
619
|
+
const runsetflags: RunSetFlags = async (
|
|
620
|
+
testspec: Json,
|
|
621
|
+
flags: Flags,
|
|
622
|
+
testsubject?: Subject,
|
|
623
|
+
) => {
|
|
624
|
+
const useflags = resolveflags(flags)
|
|
625
|
+
useflags.name = useflags.name || name || 'set'
|
|
626
|
+
|
|
627
|
+
const subject = testsubject || defsubject
|
|
628
|
+
if (null == subject) {
|
|
629
|
+
throw new OmniError('omni: no test subject for: ' + useflags.name)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const testspecmap = fixjson(testspec, useflags)
|
|
633
|
+
|
|
634
|
+
if (!ismap(testspecmap) || !islist(testspecmap.set)) {
|
|
635
|
+
throw new OmniError('omni: test spec has no set: ' + useflags.name)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const testset: Json[] = testspecmap.set
|
|
639
|
+
|
|
640
|
+
if (1 <= specversion) {
|
|
641
|
+
checkset(useflags, testspec, testset)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
for (let index = 0; index < testset.length; index++) {
|
|
645
|
+
let entry = testset[index]
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
entry = resolveentry(entry, useflags)
|
|
649
|
+
|
|
650
|
+
const testpack = resolvetestpack(name, entry, subject, useprovider, clients)
|
|
651
|
+
const args = resolveargs(entry, testpack, useprovider)
|
|
652
|
+
|
|
653
|
+
let res = await testpack.subject(...args)
|
|
654
|
+
res = fixjson(res, useflags)
|
|
655
|
+
entry.res = res
|
|
656
|
+
|
|
657
|
+
checkresult(useflags, index, entry, args, res)
|
|
658
|
+
} catch (err: any) {
|
|
659
|
+
if (err instanceof OmniError) {
|
|
660
|
+
throw err
|
|
661
|
+
}
|
|
662
|
+
handleerror(useflags, index, entry, err)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const runset: RunSet = async (testspec: Json, testsubject?: Subject) =>
|
|
668
|
+
runsetflags(testspec, {}, testsubject)
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
spec,
|
|
672
|
+
runset,
|
|
673
|
+
runsetflags,
|
|
674
|
+
subject: defsubject,
|
|
675
|
+
client: useprovider,
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export {
|
|
681
|
+
EXISTSMARK,
|
|
682
|
+
NULLMARK,
|
|
683
|
+
UNDEFMARK,
|
|
684
|
+
errify,
|
|
685
|
+
fixjson,
|
|
686
|
+
loadspec,
|
|
687
|
+
makeRunner,
|
|
688
|
+
match,
|
|
689
|
+
matchval,
|
|
690
|
+
nullmodifier,
|
|
691
|
+
resolvespec,
|
|
692
|
+
}
|