Skip to content

Commit

Permalink
Support combining multiple output variables.
Browse files Browse the repository at this point in the history
Signed-off-by: dblock <[email protected]>
  • Loading branch information
dblock committed Dec 16, 2024
1 parent ebe0f8a commit bbb33e8
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Added response schema for `PUT` and `DELETE /_plugins/_transform/{id}` ([#722](https://github.com/opensearch-project/opensearch-api-specification/pull/716))
- Added response schema for `GET /_plugins/_knn/warmup/{index}` ([#717](https://github.com/opensearch-project/opensearch-api-specification/pull/717))
- Added support for multiple test verbs ([#724](https://github.com/opensearch-project/opensearch-api-specification/pull/724))
- Added support for combining output variables ([#737](https://github.com/opensearch-project/opensearch-api-specification/pull/737))

### Removed
- Removed unsupported `_common.mapping:SourceField`'s `mode` field and associated `_common.mapping:SourceFieldMode` enum ([#652](https://github.com/opensearch-project/opensearch-api-specification/pull/652))
Expand Down
4 changes: 3 additions & 1 deletion TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ Consider the following chapters in [ml/model_groups](tests/plugins/ml/ml/model_g
```
As you can see, the `output` field in the first chapter saves the `model_group_id` from the response body. This value is then used in the subsequent chapters to query and delete the model group.
You can also supply defaults for output values, e.g. for `payload._version` used in [cluster/routing/awareness/weights.yaml](tests/routing/cluster/routing/awareness/weights.yaml).
You can combine multiple values, e.g. `task_id: ${task.node}:${task.id}`.
You can supply defaults for output values, e.g. for `payload._version` used in [cluster/routing/awareness/weights.yaml](tests/routing/cluster/routing/awareness/weights.yaml).
```
version:
Expand Down
24 changes: 17 additions & 7 deletions tests/default/tasks/list.yaml → tests/default/_core/tasks.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
$schema: ../../../json_schemas/test_story.schema.yaml

description: Test tasks list endpoint.
description: Test tasks endpoint.
chapters:
- synopsis: List tasks grouped by node.
path: /_tasks
method: GET
parameters:
group_by: nodes
response:
status: 200
- synopsis: List tasks grouped by parent.
path: /_tasks
method: GET
parameters:
group_by: parents
response:
status: 200
- synopsis: List tasks grouped by none.
id: task
path: /_tasks
method: GET
parameters:
group_by: none
response:
status: 200
output:
id: payload.tasks[0].id
node: payload.tasks[0].node
# - synopsis: Get task by id.
# id: task
# path: /_tasks/{task_id}
# method: GET
# parameters:
# task_id: ${task.node}:${task.id}
# response:
# status: 200
# payload:
# task:
# node: ${task.node}
# id: ${task.id}
7 changes: 7 additions & 0 deletions tests/default/_core/tasks/cancel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$schema: ../../../../json_schemas/test_story.schema.yaml

description: Test tasks endpoint.
chapters:
- synopsis: Cancel all tasks.
path: /_tasks/_cancel
method: POST
10 changes: 2 additions & 8 deletions tools/src/tester/StoryEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,7 @@ export default class StoryEvaluator {
static #extract_params_variables(parameters: Record<string, Parameter>, variables: Set<OutputReference>): void {
Object.values(parameters ?? {}).forEach((param) => {
if (typeof param === 'string') {
const ref = OutputReference.parse(param)
if (ref) {
variables.add(ref)
}
OutputReference.parse(param).forEach((ref) => variables.add(ref))
}
})
}
Expand All @@ -229,10 +226,7 @@ export default class StoryEvaluator {
const request_type = typeof request
switch (request_type) {
case 'string': {
const ref = OutputReference.parse(request as string)
if (ref !== undefined) {
variables.add(ref)
}
OutputReference.parse(request as string).forEach((ref) => variables.add(ref))
break
}
case 'object': {
Expand Down
13 changes: 7 additions & 6 deletions tools/src/tester/StoryOutputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ export class StoryOutputs {
}

resolve_string (str: string): any {
const ref = OutputReference.parse(str)
if (ref) {
return this.get_output_value(ref.chapter_id, ref.output_name)
} else {
return str
}
return OutputReference.replace(str, (chapter_id, output_name) => {
if (chapter_id === undefined || output_name === undefined) {
throw new Error(`Invalid output references in ${str}.`)
}

return this.get_output_value(chapter_id as string, output_name as string)
})
}

resolve_value (payload: any): any {
Expand Down
38 changes: 32 additions & 6 deletions tools/src/tester/types/eval.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,42 @@ export enum Result {
export class OutputReference {
chapter_id: string
output_name: string
private constructor (chapter_id: string, output_name: string) {

private constructor(chapter_id: string, output_name: string) {
this.chapter_id = chapter_id
this.output_name = output_name
}

static parse (str: string): OutputReference | undefined {
if (str.startsWith('${') && str.endsWith('}')) {
const spl = str.slice(2, -1).split('.', 2)
return { chapter_id: spl[0], output_name: spl[1] }
static parse(str: string): OutputReference[] {
const pattern = /\$\{([^}]+)\}/g
let match
var result = []
while ((match = pattern.exec(str)) !== null) {
const spl = this.#split(match[1], '.', 2)
result.push(new OutputReference(spl[0], spl[1]))
}
return undefined
return result
}

static replace(str: string, callback: (chapter_id: any, variable: any) => string): any {
// preserve type if 1 value is returned
let full_match = str.match(/^\$\{([^}]+)\}$/)
if (full_match) {
const spl = this.#split(full_match[1], '.', 2)
return callback(spl[0], spl[1])
} else return str.replace(/\$\{([^}]+)\}/g, (_, k) => {
const spl = this.#split(k, '.', 2)
return callback(spl[0], spl[1])
});
}

static #split(str: any, delim: string, count: number): string[] {
if (str === undefined) return [str]
if (count <= 0) return [str]
const parts = str.split(delim)
if (parts.length <= count) return parts
const result = parts.slice(0, count - 1)
result.push(parts.slice(count - 1).join(delim))
return result
}
}
10 changes: 8 additions & 2 deletions tools/tests/tester/StoryOutputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,19 @@ test('resolve_params', () => {
a: '${chapter_id.x}',
b: '${chapter_id.y}',
c: 3,
d: 'str'
d: 'str',
e: '${chapter_id.x}:${chapter_id.y}',
f: 'x=${chapter_id.x}',
g: '${chapter_id.y}=y'
}
expect(story_outputs.resolve_params(parameters)).toEqual({
a: 1,
b: 2,
c: 3,
d: 'str'
d: 'str',
e: '1:2',
f: 'x=1',
g: '2=y'
})
})

Expand Down
23 changes: 23 additions & 0 deletions tools/tests/types/eval.types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import { OutputReference } from "tester/types/eval.types";

describe('OutputReference', () => {
let f = (id: any, k: any): string => `[${id}:${k}]`

describe('replace', () => {
it('replaces', () => {
expect(OutputReference.replace('string', f)).toEqual('string')
expect(OutputReference.replace('${k.v}', f)).toEqual('[k:v]')
expect(OutputReference.replace('${k.value}', f)).toEqual('[k:value]')
expect(OutputReference.replace('${k.v.m}', f)).toEqual('[k:v.m]')
})
})
});

0 comments on commit bbb33e8

Please sign in to comment.