From 787ca3bdc26557f0302444a4b9ab0acc26d280f3 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 18:32:18 +0100 Subject: [PATCH 01/10] refactor feature to use scenario outline --- .../godog/features/model/deployment.feature | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/tests/integration/godog/features/model/deployment.feature b/tests/integration/godog/features/model/deployment.feature index ff225aa74e..fd43d6baea 100644 --- a/tests/integration/godog/features/model/deployment.feature +++ b/tests/integration/godog/features/model/deployment.feature @@ -4,16 +4,28 @@ Feature: Model deployment As a model user I need to create a Model resource and verify it is deployed - Scenario: Success - Load a model - Given I have an "iris" model + Scenario Outline: Success - Load a model + Given I have an "" model When the model is applied Then the model should eventually become Ready + Examples: + | model | + | iris | + | income-xgb | + | mnist-onnx | + | income-lgb | + | wine | + | mnist-pytorch | + | tfsimple1 | + - Scenario: Success - Load a model again + Scenario: Success - Load a model and expect status model available Given I have an "iris" model When the model is applied - Then the model should eventually become Ready + And the model eventually becomes Ready + Then the model status message should eventually be "ModelAvailable" + Scenario: Load a specific model Given I deploy model spec with timeout "10s": @@ -31,22 +43,4 @@ Feature: Model deployment """ Then the model should eventually become Ready - Scenario: Success - Load a model and expect status model available - Given I have an "iris" model - When the model is applied - And the model eventually becomes Ready - Then the model status message should eventually be "ModelAvailable" - - Scenario: Success - Load a model with min replicas - Given I have an "iris" model - And the model has "1" min replicas - When the model is applied - Then the model should eventually become Ready - -# todo: change model type - Scenario: Success - Load a big model - Given I have an "iris" model - When the model is applied - Then the model should eventually become Ready - From 47f18b2f68985a50f7fcd5d3f5a0719f522594a7 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 18:32:47 +0100 Subject: [PATCH 02/10] refactor inference with do methods --- tests/integration/godog/steps/infer.go | 69 +++++++++++++++++++++----- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/tests/integration/godog/steps/infer.go b/tests/integration/godog/steps/infer.go index ab70a1b188..3de2c0295e 100644 --- a/tests/integration/godog/steps/infer.go +++ b/tests/integration/godog/steps/infer.go @@ -25,24 +25,48 @@ import ( "google.golang.org/grpc/metadata" ) -func (i *inference) sendHTTPModelInferenceRequest(ctx context.Context, model string, payload *godog.DocString) error { - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - fmt.Sprintf("%s://%s:%d/v2/models/%s/infer", httpScheme(i.ssl), i.host, i.httpPort, model), strings.NewReader(payload.Content)) +func (i *inference) doHTTPModelInferenceRequest(ctx context.Context, modelName, body string) error { + url := fmt.Sprintf( + "%s://%s:%d/v2/models/%s/infer", + httpScheme(i.ssl), + i.host, + i.httpPort, + modelName, + ) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(body)) if err != nil { return fmt.Errorf("could not create http request: %w", err) } + req.Header.Add("Content-Type", "application/json") req.Header.Add("Host", "seldon-mesh.inference.seldon") - req.Header.Add("Seldon-model", model) + req.Header.Add("Seldon-model", modelName) resp, err := i.http.Do(req) if err != nil { return fmt.Errorf("could not send http request: %w", err) } + i.lastHTTPResponse = resp return nil } +// Used from steps that pass an explicit payload (DocString) +func (i *inference) sendHTTPModelInferenceRequest(ctx context.Context, model string, payload *godog.DocString) error { + return i.doHTTPModelInferenceRequest(ctx, model, payload.Content) +} + +// Used from steps that work from a *Model and testModels table +func (i *inference) sendHTTPModelInferenceRequestFromModel(ctx context.Context, m *Model) error { + testModel, ok := testModels[m.modelType] + if !ok { + return fmt.Errorf("could not find test model %s", m.model.Name) + } + + return i.doHTTPModelInferenceRequest(ctx, m.modelName, testModel.ValidInferenceRequest) +} + func httpScheme(useSSL bool) string { if useSSL { return "https" @@ -51,20 +75,41 @@ func httpScheme(useSSL bool) string { } func (i *inference) sendGRPCModelInferenceRequest(ctx context.Context, model string, payload *godog.DocString) error { - var msg *v2_dataplane.ModelInferRequest - if err := json.Unmarshal([]byte(payload.Content), &msg); err != nil { + return i.doGRPCModelInferenceRequest(ctx, model, payload.Content) +} + +func (i *inference) sendGRPCModelInferenceRequestFromModel(ctx context.Context, m *Model) error { + testModel, ok := testModels[m.modelType] + if !ok { + return fmt.Errorf("could not find test model %s", m.model.Name) + } + return i.doGRPCModelInferenceRequest(ctx, m.modelName, testModel.ValidInferenceRequest) +} + +func (i *inference) doGRPCModelInferenceRequest( + ctx context.Context, + model string, + payload string, +) error { + // Unmarshal into a value, then take its address when calling gRPC. + var req v2_dataplane.ModelInferRequest + if err := json.Unmarshal([]byte(payload), &req); err != nil { return fmt.Errorf("could not unmarshal gRPC json payload: %w", err) } - msg.ModelName = model + req.ModelName = model + // Attach metadata to the *existing* context, don’t discard it. md := metadata.Pairs("seldon-model", model) - ctx = metadata.NewOutgoingContext(context.Background(), md) - resp, err := i.grpc.ModelInfer(ctx, msg) - if err != nil { - i.lastGRPCResponse.err = err - } + ctx = metadata.NewOutgoingContext(ctx, md) + resp, err := i.grpc.ModelInfer(ctx, &req) + + // Record both resp and err so later steps can assert on them. i.lastGRPCResponse.response = resp + i.lastGRPCResponse.err = err + + // Important: return nil so that the step itself doesn't fail. + // The following "Then ..." step will assert on i.lastGRPCResponse.err. return nil } From a1e9e24a9b5a168588f1c8ced90e92e4ffd46719 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 18:34:36 +0100 Subject: [PATCH 03/10] preparation for default models in test cases --- tests/integration/godog/steps/model_steps.go | 113 +++++++++++++++++-- 1 file changed, 105 insertions(+), 8 deletions(-) diff --git a/tests/integration/godog/steps/model_steps.go b/tests/integration/godog/steps/model_steps.go index 2d6d7fa63e..b7e5a86daf 100644 --- a/tests/integration/godog/steps/model_steps.go +++ b/tests/integration/godog/steps/model_steps.go @@ -29,15 +29,19 @@ type Model struct { label map[string]string namespace string model *mlopsv1alpha1.Model + modelName string + modelType string k8sClient versioned.Interface watcherStorage k8sclient.WatcherStorage log logrus.FieldLogger } type TestModelConfig struct { - Name string - StorageURI string - Requirements []string // requirements might have to be applied on the applied of k8s + Name string + StorageURI string + Requirements []string // requirements might have to be applied on the applied of k8s + ValidInferenceRequest string + ValidJSONResponse string } // map to have all common testing model definitions for testing popular models @@ -48,10 +52,90 @@ var testModels = map[string]TestModelConfig{ StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/iris-sklearn", Requirements: []string{"sklearn"}, }, - "fraud-detector": { - Name: "fraud-detector", - StorageURI: "gs://other-bucket/models/fraud/", - Requirements: []string{"sklearn"}, + "income-xgb": { + Name: "income-xgb", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-xgb", + Requirements: []string{"xgboost"}, + }, + "mnist-onnx": { + Name: "mnist-onnx", + StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-onnx", + Requirements: []string{"onnx"}, + }, + "income-lgb": { + Name: "income-lgb", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-lgb", + Requirements: []string{"lightgbm"}, + }, + "wine": { + Name: "wine", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/wine-mlflow", + Requirements: []string{"mlflow"}, + }, + "mnist-pytorch": { + Name: "mnist-pytorch", + StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-pytorch", + Requirements: []string{"pytorch"}, + }, + "tfsimple1": { + Name: "tfsimple1", + StorageURI: "gs://seldon-models/triton/simple", + Requirements: []string{"tensorflow"}, + ValidInferenceRequest: `{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}`, + ValidJSONResponse: `[ + { + "name": "OUTPUT0", + "datatype": "INT32", + "shape": [ + 1, + 16 + ], + "data": [ + 2, + 4, + 6, + 8, + 10, + 12, + 14, + 16, + 18, + 20, + 22, + 24, + 26, + 28, + 30, + 32 + ] + }, + { + "name": "OUTPUT1", + "datatype": "INT32", + "shape": [ + 1, + 16 + ], + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ]`, }, } @@ -107,10 +191,22 @@ func LoadInferenceSteps(scenario *godog.ScenarioContext, w *World) { return w.infer.sendGRPCModelInferenceRequest(ctx, model, payload) }) }) + scenario.Step(`^(?:I )send a valid gRPC inference request with timeout "([^"]+)"`, func(timeout string) error { + return withTimeoutCtx(timeout, func(ctx context.Context) error { + return w.infer.sendGRPCModelInferenceRequestFromModel(ctx, w.currentModel) + }) + }) + scenario.Step(`^(?:I )send a valid HTTP inference request with timeout "([^"]+)"`, func(timeout string) error { + return withTimeoutCtx(timeout, func(ctx context.Context) error { + return w.infer.sendHTTPModelInferenceRequestFromModel(ctx, w.currentModel) + }) + }) + scenario.Step(`^expect http response status code "([^"]*)"$`, w.infer.httpRespCheckStatus) scenario.Step(`^expect http response body to contain JSON:$`, w.infer.httpRespCheckBodyContainsJSON) scenario.Step(`^expect gRPC response body to contain JSON:$`, w.infer.gRPCRespCheckBodyContainsJSON) scenario.Step(`^expect gRPC response error to contain "([^"]+)"`, w.infer.gRPCRespContainsError) + } func (m *Model) deployModelSpec(ctx context.Context, spec *godog.DocString) error { @@ -147,7 +243,8 @@ func (m *Model) IHaveAModel(model string) error { } modelName := fmt.Sprintf("%s-%s", testModel.Name, randomString(3)) - + m.modelName = modelName + m.modelType = model m.model = &mlopsv1alpha1.Model{ TypeMeta: metav1.TypeMeta{ Kind: "Model", From 64f956130348aa00afb1be1a9a46d1eb40acc81b Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 18:34:51 +0100 Subject: [PATCH 04/10] trace for watcher events --- tests/integration/godog/k8sclient/watcher_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/godog/k8sclient/watcher_store.go b/tests/integration/godog/k8sclient/watcher_store.go index cdd84cb67c..32f12531d8 100644 --- a/tests/integration/godog/k8sclient/watcher_store.go +++ b/tests/integration/godog/k8sclient/watcher_store.go @@ -90,7 +90,7 @@ func (s *WatcherStore) Start() { if err != nil { s.logger.WithError(err).Error("failed to access model watcher") } else { - s.logger.Debugf("new model watch event with name: %s on namespace: %s", accessor.GetName(), accessor.GetNamespace()) + s.logger.WithField("event", event).Tracef("new model watch event with name: %s on namespace: %s", accessor.GetName(), accessor.GetNamespace()) } if event.Object == nil { From 56a23b73470781f59554ed55d6e452775db5c529 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 18:35:23 +0100 Subject: [PATCH 05/10] default model inference --- .../godog/features/model/inference.feature | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/integration/godog/features/model/inference.feature b/tests/integration/godog/features/model/inference.feature index 06b018cb1f..ba101ff65a 100644 --- a/tests/integration/godog/features/model/inference.feature +++ b/tests/integration/godog/features/model/inference.feature @@ -1,16 +1,15 @@ -#@ModelInference @Models @Inference -#Feature Basic model inferencing -# -# Background: -# Given a clean test namespace -# -# Scenario: Model can serve prediction -# Given I have an "iris" model -# And the model is applied -# And the model eventually becomes Ready -# When I send a prediction request with payload: -# """ -# { "inputs": [1.0, 2.0, 3.0] } -# """ -# Then the response status should be 200 -# And the response body should contain "predictions" \ No newline at end of file +@ModelInference @Models @Inference +Feature: Basic model inferencing + + Scenario Outline: Success - Inference for model + Given I have an "" model + When the model is applied + Then the model should eventually become Ready + When I send a valid HTTP inference request with timeout "20s" + Then expect http response status code "200" + When I send a valid gRPC inference request with timeout "20s" + + + Examples: + | model | + | tfsimple1 | From c56a56ae4c29ad869de14866f57324d0fb8b489e Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 19:20:23 +0100 Subject: [PATCH 06/10] fix errors in godog repeating twice --- tests/integration/godog/suite/suite.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/godog/suite/suite.go b/tests/integration/godog/suite/suite.go index e9dc5d8f1e..afceca8d10 100644 --- a/tests/integration/godog/suite/suite.go +++ b/tests/integration/godog/suite/suite.go @@ -154,7 +154,9 @@ func InitializeScenario(scenarioCtx *godog.ScenarioContext) { return ctx, fmt.Errorf("error when deleting models on before steps: %w", err) } - return ctx, err + // Don't re-return stepErr, just swallow it here. + // Godog already knows the step failed and will report it. + return ctx, nil }) // Register step definitions with access to world + k8sClient From f511e714059b11e59a7f2e45e117c3aa39a89478 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 19:50:57 +0100 Subject: [PATCH 07/10] include body response when http errors occur --- tests/integration/godog/steps/infer.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/godog/steps/infer.go b/tests/integration/godog/steps/infer.go index 3de2c0295e..74b3e51522 100644 --- a/tests/integration/godog/steps/infer.go +++ b/tests/integration/godog/steps/infer.go @@ -241,7 +241,12 @@ func (i *inference) httpRespCheckStatus(status int) error { return errors.New("no http response found") } if status != i.lastHTTPResponse.StatusCode { - return fmt.Errorf("expected http response status code %d, got %d", status, i.lastHTTPResponse.StatusCode) + body, err := io.ReadAll(i.lastHTTPResponse.Body) + if err != nil { + return fmt.Errorf("expected http response status code %d, got %d", status, i.lastHTTPResponse.StatusCode) + } + return fmt.Errorf("expected http response status code %d, got %d with body: %s", status, i.lastHTTPResponse.StatusCode, body) + } return nil } From 2e74ac0d145ac4b9b7296014c9f41de60c822543 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 19:51:34 +0100 Subject: [PATCH 08/10] tidy up model responses --- tests/integration/godog/steps/model_steps.go | 97 +++++--------------- 1 file changed, 25 insertions(+), 72 deletions(-) diff --git a/tests/integration/godog/steps/model_steps.go b/tests/integration/godog/steps/model_steps.go index b7e5a86daf..20821eb298 100644 --- a/tests/integration/godog/steps/model_steps.go +++ b/tests/integration/godog/steps/model_steps.go @@ -48,94 +48,47 @@ type TestModelConfig struct { // todo: this requirements might have to be empty and automatically selected by the applier based on config if they aren't explicitly added by the scenario var testModels = map[string]TestModelConfig{ "iris": { - Name: "iris", - StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/iris-sklearn", - Requirements: []string{"sklearn"}, + Name: "iris", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/iris-sklearn", + Requirements: []string{"sklearn"}, + ValidInferenceRequest: `{"inputs": [{"name": "predict", "shape": [1, 4], "datatype": "FP32", "data": [[1, 2, 3, 4]]}]}`, }, "income-xgb": { - Name: "income-xgb", - StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-xgb", - Requirements: []string{"xgboost"}, + Name: "income-xgb", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-xgb", + Requirements: []string{"xgboost"}, + ValidInferenceRequest: `{ "parameters": {"content_type": "pd"}, "inputs": [{"name": "Age", "shape": [1, 1], "datatype": "INT64", "data": [47]},{"name": "Workclass", "shape": [1, 1], "datatype": "INT64", "data": [4]},{"name": "Education", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Marital Status", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Occupation", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Relationship", "shape": [1, 1], "datatype": "INT64", "data": [3]},{"name": "Race", "shape": [1, 1], "datatype": "INT64", "data": [4]},{"name": "Sex", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Capital Gain", "shape": [1, 1], "datatype": "INT64", "data": [0]},{"name": "Capital Loss", "shape": [1, 1], "datatype": "INT64", "data": [0]},{"name": "Hours per week", "shape": [1, 1], "datatype": "INT64", "data": [40]},{"name": "Country", "shape": [1, 1], "datatype": "INT64", "data": [9]}]}`, }, "mnist-onnx": { - Name: "mnist-onnx", - StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-onnx", - Requirements: []string{"onnx"}, + Name: "mnist-onnx", + StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-onnx", + Requirements: []string{"onnx"}, + ValidInferenceRequest: `{"inputs":[{"name":"Input3","data":[],"datatype":"FP32","shape":[]}]}`, }, "income-lgb": { - Name: "income-lgb", - StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-lgb", - Requirements: []string{"lightgbm"}, + Name: "income-lgb", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/income-lgb", + Requirements: []string{"lightgbm"}, + ValidInferenceRequest: `{"inputs": [{"name": "Age", "shape": [1, 1], "datatype": "INT64", "data": [47]},{"name": "Workclass", "shape": [1, 1], "datatype": "INT64", "data": [4]},{"name": "Education", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Marital Status", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Occupation", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Relationship", "shape": [1, 1], "datatype": "INT64", "data": [3]},{"name": "Race", "shape": [1, 1], "datatype": "INT64", "data": [4]},{"name": "Sex", "shape": [1, 1], "datatype": "INT64", "data": [1]},{"name": "Capital Gain", "shape": [1, 1], "datatype": "INT64", "data": [0]},{"name": "Capital Loss", "shape": [1, 1], "datatype": "INT64", "data": [0]},{"name": "Hours per week", "shape": [1, 1], "datatype": "INT64", "data": [40]},{"name": "Country", "shape": [1, 1], "datatype": "INT64", "data": [9]}]}`, }, "wine": { - Name: "wine", - StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/wine-mlflow", - Requirements: []string{"mlflow"}, + Name: "wine", + StorageURI: "gs://seldon-models/scv2/samples/mlserver_1.3.5/wine-mlflow", + Requirements: []string{"mlflow"}, + ValidInferenceRequest: `{ "inputs": [ { "name": "fixed acidity", "shape": [1], "datatype": "FP32", "data": [7.4] }, { "name": "volatile acidity", "shape": [1], "datatype": "FP32", "data": [0.7000] }, { "name": "citric acid", "shape": [1], "datatype": "FP32", "data": [0] }, { "name": "residual sugar", "shape": [1], "datatype": "FP32", "data": [1.9] }, { "name": "chlorides", "shape": [1], "datatype": "FP32", "data": [0.076] }, { "name": "free sulfur dioxide", "shape": [1], "datatype": "FP32", "data": [11] }, { "name": "total sulfur dioxide", "shape": [1], "datatype": "FP32", "data": [34] }, { "name": "density", "shape": [1], "datatype": "FP32", "data": [0.9978] }, { "name": "pH", "shape": [1], "datatype": "FP32", "data": [3.51] }, { "name": "sulphates", "shape": [1], "datatype": "FP32", "data": [0.56] }, { "name": "alcohol", "shape": [1], "datatype": "FP32", "data": [9.4] } ] }`, }, "mnist-pytorch": { - Name: "mnist-pytorch", - StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-pytorch", - Requirements: []string{"pytorch"}, + Name: "mnist-pytorch", + StorageURI: "gs://seldon-models/scv2/samples/triton_23-03/mnist-pytorch", + Requirements: []string{"pytorch"}, + ValidInferenceRequest: `{'inputs': [{'name': 'x__0', 'data': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3294117748737335, 0.7254902124404907, 0.6235294342041016, 0.5921568870544434, 0.23529411852359772, 0.1411764770746231, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8705882430076599, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9450980424880981, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.6666666865348816, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.26274511218070984, 0.4470588266849518, 0.2823529541492462, 0.4470588266849518, 0.6392157077789307, 0.8901960849761963, 0.9960784316062927, 0.8823529481887817, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9803921580314636, 0.8980392217636108, 0.9960784316062927, 0.9960784316062927, 0.5490196347236633, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06666667014360428, 0.25882354378700256, 0.054901961237192154, 0.26274511218070984, 0.26274511218070984, 0.26274511218070984, 0.23137255012989044, 0.08235294371843338, 0.9254902005195618, 0.9960784316062927, 0.4156862795352936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.32549020648002625, 0.9921568632125854, 0.8196078538894653, 0.07058823853731155, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08627451211214066, 0.9137254953384399, 1.0, 0.32549020648002625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5058823823928833, 0.9960784316062927, 0.9333333373069763, 0.1725490242242813, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23137255012989044, 0.9764705896377563, 0.9960784316062927, 0.24313725531101227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5215686559677124, 0.9960784316062927, 0.7333333492279053, 0.019607843831181526, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.03529411926865578, 0.8039215803146362, 0.9725490212440491, 0.22745098173618317, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4941176474094391, 0.9960784316062927, 0.7137255072593689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.29411765933036804, 0.9843137264251709, 0.9411764740943909, 0.2235294133424759, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07450980693101883, 0.8666666746139526, 0.9960784316062927, 0.6509804129600525, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0117647061124444, 0.7960784435272217, 0.9960784316062927, 0.8588235378265381, 0.13725490868091583, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14901961386203766, 0.9960784316062927, 0.9960784316062927, 0.3019607961177826, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.12156862765550613, 0.8784313797950745, 0.9960784316062927, 0.45098039507865906, 0.003921568859368563, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5215686559677124, 0.9960784316062927, 0.9960784316062927, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.239215686917305, 0.9490196108818054, 0.9960784316062927, 0.9960784316062927, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4745098054409027, 0.9960784316062927, 0.9960784316062927, 0.8588235378265381, 0.1568627506494522, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4745098054409027, 0.9960784316062927, 0.8117647171020508, 0.07058823853731155, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 'datatype': 'FP32', 'shape': [1, 1, 28, 28]}]}`, }, "tfsimple1": { Name: "tfsimple1", StorageURI: "gs://seldon-models/triton/simple", Requirements: []string{"tensorflow"}, ValidInferenceRequest: `{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}`, - ValidJSONResponse: `[ - { - "name": "OUTPUT0", - "datatype": "INT32", - "shape": [ - 1, - 16 - ], - "data": [ - 2, - 4, - 6, - 8, - 10, - 12, - 14, - 16, - 18, - 20, - 22, - 24, - 26, - 28, - 30, - 32 - ] - }, - { - "name": "OUTPUT1", - "datatype": "INT32", - "shape": [ - 1, - 16 - ], - "data": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ]`, + ValidJSONResponse: `[ { "name": "OUTPUT0", "datatype": "INT32", "shape": [ 1, 16 ], "data": [ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32 ] }, { "name": "OUTPUT1", "datatype": "INT32", "shape": [ 1, 16 ], "data": [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] } ]`, }, } From 93eb8f3c6c0e0cb0735034ff2bd5a2c312509184 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 11 Dec 2025 19:51:56 +0100 Subject: [PATCH 09/10] more models for basic inferencing --- .../godog/features/model/inference.feature | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/integration/godog/features/model/inference.feature b/tests/integration/godog/features/model/inference.feature index ba101ff65a..f87542e192 100644 --- a/tests/integration/godog/features/model/inference.feature +++ b/tests/integration/godog/features/model/inference.feature @@ -1,4 +1,4 @@ -@ModelInference @Models @Inference +@ModelInference @Models @Inference @Functional Feature: Basic model inferencing Scenario Outline: Success - Inference for model @@ -9,7 +9,12 @@ Feature: Basic model inferencing Then expect http response status code "200" When I send a valid gRPC inference request with timeout "20s" - Examples: - | model | - | tfsimple1 | + | model | + | iris | +# | income-xgb | having errors with GRPC +# | mnist-onnx | +# | income-lgb | having errors with response + | tfsimple1 | + | wine | +# | mnist-pytorch | having errors with response From ff5d893f7192ad3f50715c02e25b7bf8bb5ff0fc Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Fri, 12 Dec 2025 11:16:57 +0100 Subject: [PATCH 10/10] suppress some of the comments --- tests/integration/godog/steps/infer.go | 6 ------ tests/integration/godog/suite/suite.go | 2 -- 2 files changed, 8 deletions(-) diff --git a/tests/integration/godog/steps/infer.go b/tests/integration/godog/steps/infer.go index 74b3e51522..fab43eab37 100644 --- a/tests/integration/godog/steps/infer.go +++ b/tests/integration/godog/steps/infer.go @@ -91,25 +91,19 @@ func (i *inference) doGRPCModelInferenceRequest( model string, payload string, ) error { - // Unmarshal into a value, then take its address when calling gRPC. var req v2_dataplane.ModelInferRequest if err := json.Unmarshal([]byte(payload), &req); err != nil { return fmt.Errorf("could not unmarshal gRPC json payload: %w", err) } req.ModelName = model - // Attach metadata to the *existing* context, don’t discard it. md := metadata.Pairs("seldon-model", model) ctx = metadata.NewOutgoingContext(ctx, md) resp, err := i.grpc.ModelInfer(ctx, &req) - // Record both resp and err so later steps can assert on them. i.lastGRPCResponse.response = resp i.lastGRPCResponse.err = err - - // Important: return nil so that the step itself doesn't fail. - // The following "Then ..." step will assert on i.lastGRPCResponse.err. return nil } diff --git a/tests/integration/godog/suite/suite.go b/tests/integration/godog/suite/suite.go index afceca8d10..ded10c893e 100644 --- a/tests/integration/godog/suite/suite.go +++ b/tests/integration/godog/suite/suite.go @@ -154,8 +154,6 @@ func InitializeScenario(scenarioCtx *godog.ScenarioContext) { return ctx, fmt.Errorf("error when deleting models on before steps: %w", err) } - // Don't re-return stepErr, just swallow it here. - // Godog already knows the step failed and will report it. return ctx, nil })