Skip to content

Commit d259ea6

Browse files
committed
Add TestFlag bitfield framework for integration test behavior
Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent d6ef6a9 commit d259ea6

5 files changed

Lines changed: 363 additions & 262 deletions

File tree

cmd/osgen/api_testgen_integ.go

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ func Test{{.TypePrefix}}(t *testing.T) {
7272
testutil.SkipIfVersion(t, client, "<", {{quote .VersionAdded}}, {{quote .TypePrefix}})
7373
{{- end}}
7474
{{- end}}
75+
{{- if .WaitReady}}
76+
77+
{{- if .IsPlugin}}
78+
testutil.WaitForAllNodesReady(t, osClient)
79+
{{- else}}
80+
testutil.WaitForAllNodesReady(t, client)
81+
{{- end}}
82+
{{- end}}
7583
{{- if .NeedIndex}}
7684
7785
index := testutil.MustUniqueString(t, {{quote .IndexPrefix}})
@@ -149,6 +157,7 @@ type integTestConfig struct {
149157
NeedImportOsapitest bool
150158
IsPlugin bool
151159
IsNoBody bool
160+
WaitReady bool
152161
}
153162

154163
// renderIntegTest generates an integration test file for one operation.
@@ -187,8 +196,15 @@ func classifyOperation(op apiOperation, pkg, corePkg string, isPlugin bool) inte
187196
cfg.VersionAdded = ""
188197
}
189198

190-
if override, ok := emit.TestVersionOverrides[op.Group]; ok {
191-
cfg.VersionAdded = override
199+
flags, skipReason, skipVersion := emit.MatchRules(op.Group)
200+
if skipReason != "" {
201+
cfg.SkipReason = skipReason
202+
}
203+
if skipVersion != "" {
204+
cfg.VersionAdded = skipVersion
205+
}
206+
if flags&emit.TestWaitReady != 0 {
207+
cfg.WaitReady = true
192208
}
193209

194210
var callPrefix string
@@ -228,10 +244,6 @@ func classifyOperation(op apiOperation, pkg, corePkg string, isPlugin bool) inte
228244

229245
needsBody := op.HasBody && len(op.HTTPMethods) > 0 && op.HTTPMethods[0] != http.MethodGet
230246

231-
if skipReason := manualSetupReason(op.Group); skipReason != "" {
232-
cfg.SkipReason = skipReason
233-
}
234-
235247
cfg.IndexPrefix = "test-" + kebabCase(op.TypePrefix)
236248
if hasRequiredIndex {
237249
cfg.NeedIndex = true
@@ -251,8 +263,8 @@ func classifyOperation(op apiOperation, pkg, corePkg string, isPlugin bool) inte
251263
}
252264
cfg.NeedImportPkg = cfg.NeedIndex || cfg.FixtureCode != "" || !op.IsPointerReq || hasRequiredIndex || hasRequiredID || needsBody
253265
cfg.NeedImportCore = isPlugin && cfg.NeedIndex
254-
cfg.NeedOsClient = isPlugin && (cfg.NeedIndex || cfg.VersionAdded != "")
255-
cfg.NeedImportTestutil = !isPlugin || cfg.NeedOsClient || !op.IsNoBody || cfg.NeedIndex
266+
cfg.NeedOsClient = isPlugin && (cfg.NeedIndex || cfg.VersionAdded != "" || cfg.WaitReady)
267+
cfg.NeedImportTestutil = !isPlugin || cfg.NeedOsClient || !op.IsNoBody || cfg.NeedIndex || cfg.WaitReady
256268
cfg.NeedImportOsapitest = !op.IsNoBody
257269
cfg.NeedImportStrings = needsBody || hasRequiredID
258270

@@ -392,44 +404,6 @@ func primaryRoute(op apiOperation) struct {
392404
}{MethodName: op.TypePrefix}
393405
}
394406

395-
// manualSetupReason returns a skip reason for operations that need complex
396-
// cluster state not achievable in an automated test.
397-
func manualSetupReason(group string) string {
398-
switch {
399-
case strings.HasPrefix(group, "dangling_indices"):
400-
return "requires dangling index state from node failure"
401-
case strings.HasPrefix(group, "snapshot"):
402-
return "requires snapshot repository configuration"
403-
case group == "reindex":
404-
return "requires source index with documents"
405-
case group == "reindex_rethrottle":
406-
return "requires active reindex task"
407-
case strings.HasPrefix(group, "tasks"):
408-
return "requires active long-running task"
409-
case group == "bulk" || group == "bulk_stream":
410-
return "requires NDJSON body with action metadata"
411-
case group == "msearch" || group == "msearch_template":
412-
return "requires NDJSON multi-search body"
413-
case group == "scroll.get" || group == "scroll.delete":
414-
return "requires active scroll context"
415-
case group == "clear_scroll":
416-
return "requires active scroll context"
417-
case strings.HasPrefix(group, "pit."):
418-
return "requires point-in-time context"
419-
case group == "rank_eval":
420-
return "requires rank evaluation body with rated documents"
421-
case group == "scripts_painless.execute":
422-
return "requires painless script body"
423-
case group == "render_search_template":
424-
return "requires search template body"
425-
case group == "field_caps":
426-
return "requires index with mapped fields"
427-
case group == "termvectors" || group == "mtermvectors":
428-
return "requires index with term vectors enabled"
429-
}
430-
return ""
431-
}
432-
433407
// kebabCase converts PascalCase to kebab-case for test index prefixes.
434408
func kebabCase(s string) string {
435409
var result strings.Builder

cmd/osgen/emit/build.go

Lines changed: 9 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,6 @@ import (
1717
"github.com/opensearch-project/opensearch-go/v4/cmd/osgen/ir"
1818
)
1919

20-
// TestVersionOverrides maps operation groups to minimum server versions required
21-
// for integration tests to pass. Use this when the spec's x-version-added is
22-
// technically correct (the endpoint exists) but a server or plugin bug prevents
23-
// the test from succeeding on older versions.
24-
var TestVersionOverrides = map[string]string{
25-
// DELETE /_search/point_in_time/_all returns a malformed error body (HTTP 200
26-
// with {"error":{...}}) when no PITs exist. Fixed in 2.12 by
27-
// opensearch-project/OpenSearch#11711 (backport #11713).
28-
"delete_all_pits": "2.12",
29-
30-
// k-NN plugin's KNNScoringScriptEngine.getSupportedContexts() returned null,
31-
// causing NPE in ScriptService.getScriptLanguages() (opensearch-project/k-NN#560).
32-
"get_script_languages": "2.4",
33-
}
34-
3520
// Path field name constants used in test generation to identify fields that
3621
// require special handling (dedicated test indices, valid enum values, etc.).
3722
const (
@@ -481,11 +466,17 @@ func classifyOpIR(op *ir.Operation, pkg, corePkg string, isPlugin bool, subClien
481466
if versionAdded == "1.0.0" || versionAdded == "1.0" {
482467
versionAdded = ""
483468
}
484-
if override, ok := TestVersionOverrides[op.Group]; ok {
485-
versionAdded = override
486-
}
487469
cfg.VersionAdded = versionAdded
488470

471+
flags, skipReason, skipVersion := MatchRules(op.Group)
472+
cfg.Flags = flags
473+
if skipReason != "" {
474+
cfg.SkipReason = skipReason
475+
}
476+
if skipVersion != "" {
477+
cfg.VersionAdded = skipVersion
478+
}
479+
489480
var callPrefix string
490481
if isPlugin {
491482
suffix := op.Group
@@ -531,10 +522,6 @@ func classifyOpIR(op *ir.Operation, pkg, corePkg string, isPlugin bool, subClien
531522

532523
needsBody := op.HasBody && primary != http.MethodGet
533524

534-
if skipReason := integSkipReason(op.Group); skipReason != "" {
535-
cfg.SkipReason = skipReason
536-
}
537-
538525
cfg.ResourcePrefix = "test-" + kebabCaseIR(op.TypePrefix)
539526

540527
// Determine fixture based on operation semantics.
@@ -716,198 +703,6 @@ func buildDocFixtureIR(corePkg string, isPlugin bool) string {
716703
require.NoError(t, err)`, c, corePkg, c, corePkg, corePkg)
717704
}
718705

719-
func integSkipReason(group string) string {
720-
switch {
721-
case strings.HasPrefix(group, "dangling_indices"):
722-
return "requires dangling index state from node failure"
723-
case strings.HasPrefix(group, "snapshot"):
724-
return "requires snapshot repository configuration"
725-
case group == "reindex":
726-
return "requires source index with documents"
727-
case group == "reindex_rethrottle":
728-
return "requires active reindex task"
729-
case strings.HasPrefix(group, "tasks"):
730-
return "requires active long-running task"
731-
case group == "bulk" || group == "bulk_stream":
732-
return "requires NDJSON body with action metadata"
733-
case group == "msearch" || group == "msearch_template":
734-
return "requires NDJSON multi-search body"
735-
case group == "scroll.get" || group == "scroll.delete":
736-
return "requires active scroll context"
737-
case group == "clear_scroll":
738-
return "requires active scroll context"
739-
case strings.HasPrefix(group, "pit."):
740-
return "requires point-in-time context"
741-
case group == "rank_eval":
742-
return "requires rank evaluation body with rated documents"
743-
case group == "scripts_painless.execute" || group == "scripts_painless_execute":
744-
return "requires painless script body"
745-
case group == "render_search_template":
746-
return "requires search template body"
747-
case group == "termvectors" || group == "mtermvectors":
748-
return "requires index with term vectors enabled"
749-
case group == "indices.clone":
750-
return "requires read-only source index"
751-
case group == "indices.split":
752-
return "requires source index with number_of_routing_shards > number_of_shards"
753-
case group == "indices.shrink":
754-
return "requires source index on single node with read-only"
755-
case group == "cluster.allocation_explain":
756-
return "requires unassigned shards"
757-
case group == "cluster.post_voting_config_exclusions" ||
758-
group == "cluster.delete_voting_config_exclusions":
759-
return "requires multi-node cluster with voting configuration"
760-
case group == "cluster.put_decommission_awareness" ||
761-
group == "cluster.delete_decommission_awareness" ||
762-
group == "cluster.get_decommission_awareness":
763-
return "requires awareness attributes configured"
764-
case group == "cluster.put_weighted_routing" ||
765-
group == "cluster.get_weighted_routing" ||
766-
group == "cluster.delete_weighted_routing":
767-
return "requires awareness attributes and weighted routing setup"
768-
case group == "remote_store.restore":
769-
return "requires remote store configuration"
770-
case group == "cluster.stats":
771-
return "path builder emits /nodes segment unconditionally"
772-
case group == "_core.create" || group == "create":
773-
return "op_type=create conflicts with doc fixture"
774-
case group == "indices.add_block":
775-
return "requires valid block name (write/read/read_only/metadata)"
776-
case group == "indices.create_data_stream":
777-
return "requires matching index template with data_stream"
778-
case group == "indices.delete_data_stream":
779-
return "requires existing data stream"
780-
case group == "scroll":
781-
return "requires active scroll context"
782-
case group == "delete_by_query_rethrottle" ||
783-
group == "update_by_query_rethrottle":
784-
return "requires active long-running query task"
785-
case group == "delete_pit":
786-
return "requires active point-in-time context"
787-
case group == "nodes.hot_threads":
788-
return "returns plain text, not JSON"
789-
case group == "cat.all_pit_segments" || group == "cat.pit_segments":
790-
return "requires active point-in-time context"
791-
case group == "cat.segment_replication":
792-
return "requires segment replication enabled"
793-
case group == "cat.help":
794-
return "returns plain text, not JSON"
795-
case group == "search_pipeline.get":
796-
return "requires search pipeline to exist"
797-
case group == "search_pipeline.delete":
798-
return "requires search pipeline to exist"
799-
case group == "cat.snapshots":
800-
return "requires snapshot repository configuration"
801-
case group == "list.help":
802-
return "returns plain text, not JSON"
803-
case strings.HasPrefix(group, "list."):
804-
return "response struct does not match cat-style response format"
805-
806-
// Async search operations require a valid async search ID from a prior submit.
807-
case group == "asynchronous_search.delete" ||
808-
group == "asynchronous_search.get":
809-
return "requires async search ID from submit"
810-
case group == "asynchronous_search.stats" ||
811-
group == "asynchronous_search.search":
812-
return "response struct does not match actual response format"
813-
814-
// Flow framework operations require workflow IDs from prior create.
815-
case strings.HasPrefix(group, "flow_framework"):
816-
return "requires workflow ID from prior create"
817-
818-
// Geospatial operations require external network access or datasource setup.
819-
case strings.HasPrefix(group, "geospatial"):
820-
return "requires IP2Geo datasource or external network access"
821-
822-
// Insights top_queries requires valid metric type.
823-
case group == "insights.top_queries":
824-
return "requires valid metric type (cpu, memory, latency)"
825-
826-
// ISM operations require existing policies or policy-attached indices.
827-
case group == "ism.add_policy" || group == "ism.change_policy":
828-
return "requires policy_id in request body"
829-
case group == "ism.remove_policy" || group == "ism.retry_index":
830-
return "requires index with ISM policy attached"
831-
case group == "ism.put_policy" || group == "ism.put_policies":
832-
return "requires valid ISM policy document body"
833-
case group == "ism.delete_policy" || group == "ism.exists_policy" || group == "ism.get_policy":
834-
return "requires existing ISM policy"
835-
case group == "ism.explain_policy" || group == "ism.refresh_search_analyzers":
836-
return "response struct does not match actual response format"
837-
case group == "ism.get_policies":
838-
return "response struct does not match actual response format"
839-
840-
// KNN operations require model training infrastructure.
841-
case strings.HasPrefix(group, "knn"):
842-
return "requires KNN model training data and infrastructure"
843-
844-
// LTR operations require Learning to Rank store setup.
845-
case strings.HasPrefix(group, "ltr"):
846-
return "requires LTR feature store"
847-
848-
// ML operations require model/connector registration and deployment.
849-
case strings.HasPrefix(group, "ml"):
850-
return "requires ML model or connector registration"
851-
852-
// Neural operations require neural search model deployment.
853-
case strings.HasPrefix(group, "neural"):
854-
return "requires deployed neural search model"
855-
856-
// Notifications operations require config objects.
857-
case strings.HasPrefix(group, "notifications"):
858-
return "requires notification channel or config"
859-
860-
// Observability operations require saved objects.
861-
case strings.HasPrefix(group, "observability"):
862-
return "requires observability saved object"
863-
864-
// PPL/SQL operations require valid query bodies.
865-
case strings.HasPrefix(group, "ppl"):
866-
return "requires valid PPL query body"
867-
case strings.HasPrefix(group, "sql"):
868-
return "requires valid SQL query body"
869-
case strings.HasPrefix(group, "query"):
870-
return "requires valid query DSL body"
871-
872-
// Replication requires cross-cluster setup.
873-
case strings.HasPrefix(group, "replication"):
874-
return "requires cross-cluster replication setup"
875-
876-
// Rollups require rollup job definition.
877-
case strings.HasPrefix(group, "rollups"):
878-
return "requires rollup job configuration"
879-
880-
// Security plugin operations require security resources.
881-
case strings.HasPrefix(group, "security"):
882-
return "requires security plugin resources"
883-
884-
// Snapshot management requires SM policy.
885-
case strings.HasPrefix(group, "sm"):
886-
return "requires snapshot management policy"
887-
888-
// Transforms require transform job.
889-
case strings.HasPrefix(group, "transforms"):
890-
return "requires transform job configuration"
891-
892-
// UBI plugin requires initialization state not available in test clusters.
893-
case strings.HasPrefix(group, "ubi"):
894-
return "requires UBI plugin initialization"
895-
896-
// Ingestion operations require an index with pull-based ingestion source (e.g. Kafka).
897-
case strings.HasPrefix(group, "ingestion"):
898-
return "requires index with pull-based ingestion source configured"
899-
900-
// Search relevance operations require existing resources (experiments, query sets).
901-
case strings.HasPrefix(group, "search_relevance"):
902-
return "requires search relevance resources (experiments, query sets)"
903-
904-
// WLM operations require workload group setup.
905-
case strings.HasPrefix(group, "wlm"):
906-
return "requires workload management query group"
907-
}
908-
return ""
909-
}
910-
911706
type fixtureType int
912707

913708
const (

0 commit comments

Comments
 (0)