diff options
30 files changed, 324 insertions, 221 deletions
diff --git a/client/go/internal/cli/cmd/curl.go b/client/go/internal/cli/cmd/curl.go index c7297574a32..63f6ef8c592 100644 --- a/client/go/internal/cli/cmd/curl.go +++ b/client/go/internal/cli/cmd/curl.go @@ -37,7 +37,7 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain if err != nil { return err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) service, err := waiter.Service(target, cli.config.cluster()) if err != nil { return err diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go index 9ae6676bc17..718e2b27b2b 100644 --- a/client/go/internal/cli/cmd/deploy.go +++ b/client/go/internal/cli/cmd/deploy.go @@ -59,7 +59,6 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, if err != nil { return err } - timeout := time.Duration(waitSecs) * time.Second opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target} if versionArg != "" { version, err := version.Parse(versionArg) @@ -73,15 +72,16 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, return err } } - waiter := cli.waiter(timeout) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) if _, err := waiter.DeployService(target); err != nil { return err } var result vespa.PrepareResult - if err := cli.spinner(cli.Stderr, "Uploading application package...", func() error { + err = cli.spinner(cli.Stderr, "Uploading application package...", func() error { result, err = vespa.Deploy(opts) return err - }); err != nil { + }) + if err != nil { return err } log.Println() @@ -95,7 +95,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`, log.Printf("\nUse %s for deployment status, or follow this deployment at", color.CyanString("vespa status deployment")) log.Print(color.CyanString(opts.Target.Deployment().System.ConsoleRunURL(opts.Target.Deployment(), result.ID))) } - return waitForDeploymentReady(cli, target, result.ID, timeout) + return waitForVespaReady(target, result.ID, waiter) }, } cmd.Flags().StringVarP(&logLevelArg, "log-level", "l", "error", `Log level for Vespa logs. Must be "error", "warning", "info" or "debug"`) @@ -157,8 +157,7 @@ func newActivateCmd(cli *CLI) *cobra.Command { if err != nil { return err } - timeout := time.Duration(waitSecs) * time.Second - waiter := cli.waiter(timeout) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) if _, err := waiter.DeployService(target); err != nil { return err } @@ -168,23 +167,28 @@ func newActivateCmd(cli *CLI) *cobra.Command { return err } cli.printSuccess("Activated application with session ", sessionID) - return waitForDeploymentReady(cli, target, sessionID, timeout) + return waitForVespaReady(target, sessionID, waiter) }, } cli.bindWaitFlag(cmd, 0, &waitSecs) return cmd } -func waitForDeploymentReady(cli *CLI, target vespa.Target, sessionOrRunID int64, timeout time.Duration) error { - if timeout == 0 { - return nil - } - waiter := cli.waiter(timeout) - if _, err := waiter.Deployment(target, sessionOrRunID); err != nil { - return err +func waitForVespaReady(target vespa.Target, sessionOrRunID int64, waiter *Waiter) error { + fastWait := waiter.FastWaitOn(target) + hasTimeout := waiter.Timeout > 0 + if fastWait || hasTimeout { + // Wait for deployment convergence + if _, err := waiter.Deployment(target, sessionOrRunID); err != nil { + return err + } + // Wait for healthy services + if hasTimeout { + _, err := waiter.Services(target) + return err + } } - _, err := waiter.Services(target) - return err + return nil } func printPrepareLog(stderr io.Writer, result vespa.PrepareResult) { diff --git a/client/go/internal/cli/cmd/deploy_test.go b/client/go/internal/cli/cmd/deploy_test.go index 4e32b9bbd60..4771d107ba4 100644 --- a/client/go/internal/cli/cmd/deploy_test.go +++ b/client/go/internal/cli/cmd/deploy_test.go @@ -62,6 +62,46 @@ Hint: Pass --add-cert to use the certificate of the current application assert.Contains(t, stdout.String(), "Success: Triggered deployment") } +func TestDeployCloudFastWait(t *testing.T) { + pkgDir := filepath.Join(t.TempDir(), "app") + createApplication(t, pkgDir, false, false) + + cli, stdout, stderr := newTestCLI(t, "CI=true") + httpClient := &mock.HTTPClient{} + cli.httpClient = httpClient + + app := vespa.ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"} + assert.Nil(t, cli.Run("config", "set", "application", app.String())) + assert.Nil(t, cli.Run("config", "set", "target", "cloud")) + assert.Nil(t, cli.Run("auth", "api-key")) + assert.Nil(t, cli.Run("auth", "cert", pkgDir)) + + // Deployment completes quickly + httpClient.NextResponseString(200, `ok`) + httpClient.NextResponseString(200, `{"active": false, "status": "success"}`) + require.Nil(t, cli.Run("deploy", pkgDir)) + assert.Contains(t, stdout.String(), "Success: Triggered deployment") + assert.True(t, httpClient.Consumed()) + + // Deployment fails quickly + stdout.Reset() + stderr.Reset() + httpClient.NextResponseString(200, `ok`) + httpClient.NextResponseString(200, `{"active": false, "status": "unsuccesful"}`) + require.NotNil(t, cli.Run("deploy", pkgDir)) + assert.Equal(t, stderr.String(), "Error: deployment run 0 not yet complete after waiting up to 2s: aborting wait: deployment failed: run 0 ended with unsuccessful status: unsuccesful\n") + assert.True(t, httpClient.Consumed()) + + // Deployment which is running does not return error + stdout.Reset() + stderr.Reset() + httpClient.NextResponseString(200, `ok`) + httpClient.NextResponseString(200, `{"active": true, "status": "running"}`) + require.Nil(t, cli.Run("deploy", pkgDir)) + assert.Contains(t, stdout.String(), "Success: Triggered deployment") + assert.True(t, httpClient.Consumed()) +} + func TestDeployWait(t *testing.T) { cli, stdout, _ := newTestCLI(t) client := &mock.HTTPClient{} diff --git a/client/go/internal/cli/cmd/document.go b/client/go/internal/cli/cmd/document.go index 0393a9b2595..ea99488ee90 100644 --- a/client/go/internal/cli/cmd/document.go +++ b/client/go/internal/cli/cmd/document.go @@ -27,8 +27,8 @@ func addDocumentFlags(cli *CLI, cmd *cobra.Command, printCurl *bool, timeoutSecs cli.bindWaitFlag(cmd, 0, waitSecs) } -func documentClient(cli *CLI, timeoutSecs, waitSecs int, printCurl bool) (*document.Client, *vespa.Service, error) { - docService, err := documentService(cli, waitSecs) +func documentClient(cli *CLI, timeoutSecs int, waiter *Waiter, printCurl bool) (*document.Client, *vespa.Service, error) { + docService, err := documentService(cli, waiter) if err != nil { return nil, nil, err } @@ -47,8 +47,8 @@ func documentClient(cli *CLI, timeoutSecs, waitSecs int, printCurl bool) (*docum return client, docService, nil } -func sendOperation(op document.Operation, args []string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error { - client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) +func sendOperation(op document.Operation, args []string, timeoutSecs int, waiter *Waiter, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, waiter, printCurl) if err != nil { return err } @@ -91,8 +91,8 @@ func sendOperation(op document.Operation, args []string, timeoutSecs, waitSecs i return printResult(cli, operationResult(false, doc, service, result), false) } -func readDocument(id string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error { - client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) +func readDocument(id string, timeoutSecs int, waiter *Waiter, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, waiter, printCurl) if err != nil { return err } @@ -146,7 +146,8 @@ should be used instead of this.`, SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(-1, args, timeoutSecs, waitSecs, printCurl, cli) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + return sendOperation(-1, args, timeoutSecs, waiter, printCurl, cli) }, } addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) @@ -171,7 +172,8 @@ $ vespa document put id:mynamespace:music::a-head-full-of-dreams src/test/resour DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(document.OperationPut, args, timeoutSecs, waitSecs, printCurl, cli) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + return sendOperation(document.OperationPut, args, timeoutSecs, waiter, printCurl, cli) }, } addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) @@ -195,7 +197,8 @@ $ vespa document update id:mynamespace:music::a-head-full-of-dreams src/test/res DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - return sendOperation(document.OperationUpdate, args, timeoutSecs, waitSecs, printCurl, cli) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + return sendOperation(document.OperationUpdate, args, timeoutSecs, waiter, printCurl, cli) }, } addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) @@ -219,8 +222,9 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`, DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) if strings.HasPrefix(args[0], "id:") { - client, service, err := documentClient(cli, timeoutSecs, waitSecs, printCurl) + client, service, err := documentClient(cli, timeoutSecs, waiter, printCurl) if err != nil { return err } @@ -232,7 +236,7 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`, result := client.Send(doc) return printResult(cli, operationResult(false, doc, service, result), false) } else { - return sendOperation(document.OperationRemove, args, timeoutSecs, waitSecs, printCurl, cli) + return sendOperation(document.OperationRemove, args, timeoutSecs, waiter, printCurl, cli) } }, } @@ -254,19 +258,19 @@ func newDocumentGetCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Example: `$ vespa document get id:mynamespace:music::a-head-full-of-dreams`, RunE: func(cmd *cobra.Command, args []string) error { - return readDocument(args[0], timeoutSecs, waitSecs, printCurl, cli) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + return readDocument(args[0], timeoutSecs, waiter, printCurl, cli) }, } addDocumentFlags(cli, cmd, &printCurl, &timeoutSecs, &waitSecs) return cmd } -func documentService(cli *CLI, waitSecs int) (*vespa.Service, error) { +func documentService(cli *CLI, waiter *Waiter) (*vespa.Service, error) { target, err := cli.target(targetOptions{}) if err != nil { return nil, err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) return waiter.Service(target, cli.config.cluster()) } diff --git a/client/go/internal/cli/cmd/feed.go b/client/go/internal/cli/cmd/feed.go index 69b847547a9..f69de6b3038 100644 --- a/client/go/internal/cli/cmd/feed.go +++ b/client/go/internal/cli/cmd/feed.go @@ -108,7 +108,7 @@ $ cat docs.jsonl | vespa feed -`, pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } - err := feed(args, options, cli) + err := feed(args, options, cli, cmd) if options.memprofile != "" { f, err := os.Create(options.memprofile) if err != nil { @@ -124,7 +124,7 @@ $ cat docs.jsonl | vespa feed -`, return cmd } -func createServices(n int, timeout time.Duration, waitSecs int, cli *CLI) ([]httputil.Client, string, error) { +func createServices(n int, timeout time.Duration, cli *CLI, waiter *Waiter) ([]httputil.Client, string, error) { if n < 1 { return nil, "", fmt.Errorf("need at least one client") } @@ -134,7 +134,6 @@ func createServices(n int, timeout time.Duration, waitSecs int, cli *CLI) ([]htt } services := make([]httputil.Client, 0, n) baseURL := "" - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) for i := 0; i < n; i++ { service, err := waiter.Service(target, cli.config.cluster()) if err != nil { @@ -228,9 +227,10 @@ func enqueueAndWait(files []string, dispatcher *document.Dispatcher, options fee return fmt.Errorf("at least one file to feed from must specified") } -func feed(files []string, options feedOptions, cli *CLI) error { +func feed(files []string, options feedOptions, cli *CLI, cmd *cobra.Command) error { timeout := time.Duration(options.timeoutSecs) * time.Second - clients, baseURL, err := createServices(options.connections, timeout, options.waitSecs, cli) + waiter := cli.waiter(time.Duration(options.waitSecs)*time.Second, cmd) + clients, baseURL, err := createServices(options.connections, timeout, cli, waiter) if err != nil { return err } diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go index 620ec055a1d..08d4086719d 100644 --- a/client/go/internal/cli/cmd/prod.go +++ b/client/go/internal/cli/cmd/prod.go @@ -430,6 +430,6 @@ func verifyTest(cli *CLI, testsParent string, suite string, required bool) error } return nil } - _, _, err = runTests(cli, testDirectory, true, 0) + _, _, err = runTests(cli, testDirectory, true, nil) return err } diff --git a/client/go/internal/cli/cmd/query.go b/client/go/internal/cli/cmd/query.go index db6dfa0a158..6feead66082 100644 --- a/client/go/internal/cli/cmd/query.go +++ b/client/go/internal/cli/cmd/query.go @@ -45,7 +45,8 @@ can be set by the syntax [parameter-name]=[value].`, SilenceUsage: true, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return query(cli, args, queryTimeoutSecs, waitSecs, printCurl, format, headers) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + return query(cli, args, queryTimeoutSecs, printCurl, format, headers, waiter) }, } cmd.Flags().BoolVarP(&printCurl, "verbose", "v", false, "Print the equivalent curl command for the query") @@ -81,12 +82,11 @@ func parseHeaders(headers []string) (http.Header, error) { return h, nil } -func query(cli *CLI, arguments []string, timeoutSecs, waitSecs int, curl bool, format string, headers []string) error { +func query(cli *CLI, arguments []string, timeoutSecs int, curl bool, format string, headers []string, waiter *Waiter) error { target, err := cli.target(targetOptions{}) if err != nil { return err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) service, err := waiter.Service(target, cli.config.cluster()) if err != nil { return err diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go index 8e0f3de4f72..6d0c747954a 100644 --- a/client/go/internal/cli/cmd/root.go +++ b/client/go/internal/cli/cmd/root.go @@ -54,6 +54,7 @@ type CLI struct { now func() time.Time retryInterval time.Duration + waitTimeout *time.Duration cmd *cobra.Command config *Config @@ -376,7 +377,9 @@ func (c *CLI) confirm(question string, confirmByDefault bool) (bool, error) { } } -func (c *CLI) waiter(timeout time.Duration) *Waiter { return &Waiter{Timeout: timeout, cli: c} } +func (c *CLI) waiter(timeout time.Duration, cmd *cobra.Command) *Waiter { + return &Waiter{Timeout: timeout, cli: c, cmd: cmd} +} // target creates a target according the configuration of this CLI and given opts. func (c *CLI) target(opts targetOptions) (vespa.Target, error) { diff --git a/client/go/internal/cli/cmd/status.go b/client/go/internal/cli/cmd/status.go index 6056ee439b2..d660e71abf9 100644 --- a/client/go/internal/cli/cmd/status.go +++ b/client/go/internal/cli/cmd/status.go @@ -5,6 +5,7 @@ package cmd import ( + "errors" "fmt" "log" "strconv" @@ -49,7 +50,7 @@ $ vepsa status --format plain --cluster mycluster`, if err := verifyFormat(format); err != nil { return err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) var failingContainers []*vespa.Service if cluster == "" { services, err := waiter.Services(t) @@ -125,7 +126,7 @@ func newStatusDeployCmd(cli *CLI) *cobra.Command { if err := verifyFormat(format); err != nil { return err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) s, err := waiter.DeployService(t) if err != nil { return err @@ -173,11 +174,11 @@ $ vespa status deployment -t local [session-id] --wait 600 if err != nil { return err } - waiter := cli.waiter(time.Duration(waitSecs) * time.Second) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) id, err := waiter.Deployment(t, wantedID) if err != nil { var hints []string - if waiter.Timeout == 0 { + if waiter.Timeout == 0 && !errors.Is(err, vespa.ErrDeployment) { hints = []string{"Consider using the --wait flag to wait for completion"} } return ErrCLI{Status: 1, warn: true, hints: hints, error: err} diff --git a/client/go/internal/cli/cmd/status_test.go b/client/go/internal/cli/cmd/status_test.go index 5ef96c462d8..bf9b4f3493e 100644 --- a/client/go/internal/cli/cmd/status_test.go +++ b/client/go/internal/cli/cmd/status_test.go @@ -85,7 +85,7 @@ func TestStatusError(t *testing.T) { cli.httpClient = client assert.NotNil(t, cli.Run("status", "container")) assert.Equal(t, - "Container default at http://127.0.0.1:8080 is not ready: unhealthy container default: status 500 at http://127.0.0.1:8080/status.html: giving up\n", + "Container default at http://127.0.0.1:8080 is not ready: unhealthy container default: status 500 at http://127.0.0.1:8080/status.html: wait deadline reached\n", stdout.String()) assert.Equal(t, "Error: services not ready: default\n", @@ -122,13 +122,13 @@ func TestStatusLocalDeployment(t *testing.T) { resp.Body = []byte(`{"currentGeneration": 42, "converged": false}`) client.NextResponse(resp) assert.NotNil(t, cli.Run("status", "deployment")) - assert.Equal(t, "Warning: deployment not converged on latest generation: giving up\nHint: Consider using the --wait flag to wait for completion\n", stderr.String()) + assert.Equal(t, "Warning: deployment not converged on latest generation: wait deadline reached\nHint: Consider using the --wait flag to wait for completion\n", stderr.String()) // Explicit generation stderr.Reset() client.NextResponse(resp) assert.NotNil(t, cli.Run("status", "deployment", "41")) - assert.Equal(t, "Warning: deployment not converged on generation 41: giving up\nHint: Consider using the --wait flag to wait for completion\n", stderr.String()) + assert.Equal(t, "Warning: deployment not converged on generation 41: wait deadline reached\nHint: Consider using the --wait flag to wait for completion\n", stderr.String()) } func TestStatusCloudDeployment(t *testing.T) { @@ -164,7 +164,7 @@ func TestStatusCloudDeployment(t *testing.T) { Body: []byte(`{"active": false, "status": "failure"}`), }) assert.NotNil(t, cli.Run("status", "deployment", "42", "-w", "10")) - assert.Equal(t, "Waiting up to 10s for deployment to converge...\nWarning: deployment run 42 incomplete after waiting up to 10s: aborting wait: run 42 ended with unsuccessful status: failure\n", stderr.String()) + assert.Equal(t, "Waiting up to 10s for deployment to converge...\nWarning: deployment run 42 not yet complete after waiting up to 10s: aborting wait: deployment failed: run 42 ended with unsuccessful status: failure\n", stderr.String()) } func isLocalTarget(args []string) bool { diff --git a/client/go/internal/cli/cmd/test.go b/client/go/internal/cli/cmd/test.go index 3bc78fc91c8..5144abe95b4 100644 --- a/client/go/internal/cli/cmd/test.go +++ b/client/go/internal/cli/cmd/test.go @@ -42,7 +42,8 @@ $ vespa test src/test/application/tests/system-test/feed-and-query.json`, DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - count, failed, err := runTests(cli, args[0], false, waitSecs) + waiter := cli.waiter(time.Duration(waitSecs)*time.Second, cmd) + count, failed, err := runTests(cli, args[0], false, waiter) if err != nil { return err } @@ -70,7 +71,7 @@ $ vespa test src/test/application/tests/system-test/feed-and-query.json`, return testCmd } -func runTests(cli *CLI, rootPath string, dryRun bool, waitSecs int) (int, []string, error) { +func runTests(cli *CLI, rootPath string, dryRun bool, waiter *Waiter) (int, []string, error) { count := 0 failed := make([]string, 0) if stat, err := os.Stat(rootPath); err != nil { @@ -89,7 +90,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool, waitSecs int) (int, []stri fmt.Fprintln(cli.Stdout, "") previousFailed = false } - failure, err := runTest(testPath, context, waitSecs) + failure, err := runTest(testPath, context, waiter) if err != nil { return 0, nil, err } @@ -101,7 +102,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool, waitSecs int) (int, []stri } } } else if strings.HasSuffix(stat.Name(), ".json") { - failure, err := runTest(rootPath, testContext{testsPath: filepath.Dir(rootPath), dryRun: dryRun, cli: cli, clusters: map[string]*vespa.Service{}}, waitSecs) + failure, err := runTest(rootPath, testContext{testsPath: filepath.Dir(rootPath), dryRun: dryRun, cli: cli, clusters: map[string]*vespa.Service{}}, waiter) if err != nil { return 0, nil, err } @@ -117,7 +118,7 @@ func runTests(cli *CLI, rootPath string, dryRun bool, waitSecs int) (int, []stri } // Runs the test at the given path, and returns the specified test name if the test fails -func runTest(testPath string, context testContext, waitSecs int) (string, error) { +func runTest(testPath string, context testContext, waiter *Waiter) (string, error) { var test test testBytes, err := os.ReadFile(testPath) if err != nil { @@ -150,7 +151,7 @@ func runTest(testPath string, context testContext, waitSecs int) (string, error) if step.Name != "" { stepName += ": " + step.Name } - failure, longFailure, err := verify(step, test.Defaults.Cluster, defaultParameters, context, waitSecs) + failure, longFailure, err := verify(step, test.Defaults.Cluster, defaultParameters, context, waiter) if err != nil { fmt.Fprintln(context.cli.Stderr) return "", errHint(fmt.Errorf("error in %s: %w", stepName, err), "See https://docs.vespa.ai/en/reference/testing") @@ -173,7 +174,7 @@ func runTest(testPath string, context testContext, waitSecs int) (string, error) } // Asserts specified response is obtained for request, or returns a failure message, or an error if this fails -func verify(step step, defaultCluster string, defaultParameters map[string]string, context testContext, waitSecs int) (string, string, error) { +func verify(step step, defaultCluster string, defaultParameters map[string]string, context testContext, waiter *Waiter) (string, string, error) { requestBody, err := getBody(step.Request.BodyRaw, context.testsPath) if err != nil { return "", "", err @@ -227,9 +228,8 @@ func verify(step step, defaultCluster string, defaultParameters map[string]strin } ok := false service, ok = context.clusters[cluster] - if !ok { + if !ok && waiter != nil { // Cache service so we don't have to discover it for every step - waiter := context.cli.waiter(time.Duration(waitSecs) * time.Second) service, err = waiter.Service(target, cluster) if err != nil { return "", "", err diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go index 963833337c2..7f99df2038e 100644 --- a/client/go/internal/cli/cmd/visit.go +++ b/client/go/internal/cli/cmd/visit.go @@ -109,7 +109,8 @@ $ vespa visit --field-set "[id]" # list document IDs if !result.Success { return fmt.Errorf("argument error: %s", result.Message) } - service, err := documentService(cli, vArgs.waitSecs) + waiter := cli.waiter(time.Duration(vArgs.waitSecs)*time.Second, cmd) + service, err := documentService(cli, waiter) if err != nil { return err } diff --git a/client/go/internal/cli/cmd/visit_test.go b/client/go/internal/cli/cmd/visit_test.go index 3053b987838..ae494059691 100644 --- a/client/go/internal/cli/cmd/visit_test.go +++ b/client/go/internal/cli/cmd/visit_test.go @@ -97,7 +97,7 @@ func withMockClient(t *testing.T, prepCli func(*mock.HTTPClient), runOp func(*ve prepCli(client) cli, _, _ := newTestCLI(t) cli.httpClient = client - service, err := documentService(cli, 0) + service, err := documentService(cli, &Waiter{cli: cli}) if err != nil { t.Fatal(err) } diff --git a/client/go/internal/cli/cmd/waiter.go b/client/go/internal/cli/cmd/waiter.go index d818359e61c..8a25e18cd1e 100644 --- a/client/go/internal/cli/cmd/waiter.go +++ b/client/go/internal/cli/cmd/waiter.go @@ -2,10 +2,12 @@ package cmd import ( + "errors" "fmt" "time" "github.com/fatih/color" + "github.com/spf13/cobra" "github.com/vespa-engine/vespa/client/go/internal/vespa" ) @@ -15,6 +17,7 @@ type Waiter struct { Timeout time.Duration // TODO(mpolden): Consider making this a budget cli *CLI + cmd *cobra.Command } // DeployService returns the service providing the deploy API on given target, @@ -81,10 +84,30 @@ func (w *Waiter) services(target vespa.Target) ([]*vespa.Service, error) { return target.ContainerServices(w.Timeout) } +// FastWaitOn returns whether we should use a short default timeout for given target. +func (w *Waiter) FastWaitOn(target vespa.Target) bool { + return target.IsCloud() && w.Timeout == 0 && !w.cmd.PersistentFlags().Changed("wait") +} + // Deployment waits for a deployment to become ready, returning the ID of the converged deployment. -func (w *Waiter) Deployment(target vespa.Target, id int64) (int64, error) { - if w.Timeout > 0 { - w.cli.printInfo("Waiting up to ", color.CyanString(w.Timeout.String()), " for deployment to converge...") +func (w *Waiter) Deployment(target vespa.Target, wantedID int64) (int64, error) { + timeout := w.Timeout + fastWait := w.FastWaitOn(target) + if timeout > 0 { + w.cli.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for deployment to converge...") + } else if fastWait { + // If --wait is not explicitly given, we always wait a few seconds in Cloud to catch fast failures, e.g. + // invalid application package + timeout = 2 * time.Second + } + id, err := target.AwaitDeployment(wantedID, timeout) + if errors.Is(err, vespa.ErrWaitTimeout) { + if fastWait { + return id, nil // Do not report fast wait timeout as an error + } + if target.IsCloud() { + w.cli.printInfo("Timed out waiting for deployment to converge. See ", color.CyanString(target.Deployment().System.ConsoleRunURL(target.Deployment(), wantedID)), " for more details") + } } - return target.AwaitDeployment(id, w.Timeout) + return id, err } diff --git a/client/go/internal/mock/http.go b/client/go/internal/mock/http.go index 2fbaa85ecca..8274f4113d3 100644 --- a/client/go/internal/mock/http.go +++ b/client/go/internal/mock/http.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "os" "strconv" "time" ) @@ -17,6 +18,9 @@ type HTTPClient struct { // The errors to return for future requests. If non-nil, these errors are returned before any responses in nextResponses. nextErrors []error + // LogRequests enables logging of all requests made through this client. + LogRequests bool + // LastRequest is the last HTTP request made through this. LastRequest *http.Request @@ -56,7 +60,12 @@ func (c *HTTPClient) NextResponseError(err error) { c.nextErrors = append(c.nextErrors, err) } +func (c *HTTPClient) Consumed() bool { return len(c.nextResponses) == 0 } + func (c *HTTPClient) Do(request *http.Request, timeout time.Duration) (*http.Response, error) { + if c.LogRequests { + fmt.Fprintf(os.Stderr, "Sending request %+v\n", request) + } c.LastRequest = request if len(c.nextErrors) > 0 { err := c.nextErrors[0] diff --git a/client/go/internal/vespa/target.go b/client/go/internal/vespa/target.go index 94eb2cbafe4..960917b75d6 100644 --- a/client/go/internal/vespa/target.go +++ b/client/go/internal/vespa/target.go @@ -36,9 +36,15 @@ const ( AnyDeployment int64 = -2 ) -var errWaitTimeout = errors.New("giving up") var errAuth = errors.New("auth failed") +var ( + // ErrWaitTimeout is the error returned when waiting for something times out. + ErrWaitTimeout = errors.New("wait deadline reached") + // ErrDeployment is the error returned for terminal deployment failures. + ErrDeployment = errors.New("deployment failed") +) + // Authenticator authenticates the given HTTP request. type Authenticator interface { Authenticate(request *http.Request) error @@ -290,7 +296,7 @@ func wait(service *Service, okFn responseFunc, reqFn requestFunc, timeout, retry time.Sleep(retryInterval) } if err == nil { - return status, errWaitTimeout + return status, ErrWaitTimeout } return status, err } diff --git a/client/go/internal/vespa/target_cloud.go b/client/go/internal/vespa/target_cloud.go index c063b99edef..6883515cee5 100644 --- a/client/go/internal/vespa/target_cloud.go +++ b/client/go/internal/vespa/target_cloud.go @@ -233,7 +233,7 @@ func (t *cloudTarget) PrintLog(options LogOptions) error { timeout = math.MaxInt64 // No timeout } // Ignore wait error because logFunc has no concept of completion, we just want to print log entries until timeout is reached - if _, err := t.deployServiceWait(logFunc, requestFunc, timeout); err != nil && !errors.Is(err, errWaitTimeout) { + if _, err := t.deployServiceWait(logFunc, requestFunc, timeout); err != nil && !errors.Is(err, ErrWaitTimeout) { return fmt.Errorf("failed to read logs: %s", err) } return nil @@ -309,17 +309,17 @@ func (t *cloudTarget) AwaitDeployment(runID int64, timeout time.Duration) (int64 return false, nil } if resp.Status != "success" { - return false, fmt.Errorf("run %d ended with unsuccessful status: %s", runID, resp.Status) + return false, fmt.Errorf("%w: run %d ended with unsuccessful status: %s", ErrDeployment, runID, resp.Status) } success = true return success, nil } _, err = t.deployServiceWait(jobSuccessFunc, requestFunc, timeout) if err != nil { - return 0, fmt.Errorf("deployment run %d incomplete%s: %w", runID, waitDescription(timeout), err) + return 0, fmt.Errorf("deployment run %d not yet complete%s: %w", runID, waitDescription(timeout), err) } if !success { - return 0, fmt.Errorf("deployment run %d incomplete%s", runID, waitDescription(timeout)) + return 0, fmt.Errorf("deployment run %d not yet complete%s", runID, waitDescription(timeout)) } return runID, nil } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java index 03b96b12c03..364048ff261 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java @@ -19,11 +19,13 @@ public class OpenTelemetryCollector extends AbstractService implements OpenTelem private final Zone zone; private final ApplicationId applicationId; + private final boolean isHostedVespa; public OpenTelemetryCollector(TreeConfigProducer<?> parent) { super(parent, "otelcol"); this.zone = null; this.applicationId = null; + this.isHostedVespa = false; setProp("clustertype", "admin"); setProp("clustername", "admin"); } @@ -32,6 +34,7 @@ public class OpenTelemetryCollector extends AbstractService implements OpenTelem super(parent, "otelcol"); this.zone = deployState.zone(); this.applicationId = deployState.getProperties().applicationId(); + this.isHostedVespa = deployState.isHosted(); setProp("clustertype", "admin"); setProp("clustername", "admin"); } @@ -54,7 +57,7 @@ public class OpenTelemetryCollector extends AbstractService implements OpenTelem @Override public void getConfig(OpenTelemetryConfig.Builder builder) { - var generator = new OpenTelemetryConfigGenerator(zone, applicationId); + var generator = new OpenTelemetryConfigGenerator(zone, applicationId, isHostedVespa); AnyConfigProducer pp = this; AnyConfigProducer p = pp.getParent(); while (p != null && p != pp) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java index 9875cf434aa..43a13a3e7c1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import static com.yahoo.vespa.defaults.Defaults.getDefaults; @@ -35,10 +36,12 @@ public class OpenTelemetryConfigGenerator { private List<StatePortInfo> statePorts = new ArrayList<>(); private final Zone zone; private final ApplicationId applicationId; + private final boolean isHostedVespa; - OpenTelemetryConfigGenerator(Zone zone, ApplicationId applicationId) { + OpenTelemetryConfigGenerator(Zone zone, ApplicationId applicationId, boolean isHostedVespa) { this.zone = zone; this.applicationId = applicationId; + this.isHostedVespa = isHostedVespa; boolean isCd = true; boolean isPublic = true; if (zone != null) { @@ -103,10 +106,29 @@ public class OpenTelemetryConfigGenerator { g.writeStringField(entry.getKey(), entry.getValue()); } } + String ph = findParentHost(statePort.hostName()); + if (isHostedVespa && ph != null) { + g.writeStringField("parentHostname", ph); + } g.writeEndObject(); } g.writeEndObject(); } + // note: this pattern should match entire node name + static private final Pattern expectedNodeName1 = Pattern.compile("[a-z0-9]+-v6-[0-9]+[.].+"); + static private final Pattern expectedNodeName2 = Pattern.compile("[a-z]*[0-9]+[a-z][.].+"); + // matches the part we want to replace with just a dot + static private final Pattern replaceNodeName1 = Pattern.compile("-v6-[0-9]+[.]"); + static private final Pattern replaceNodeName2 = Pattern.compile("[a-z][.]"); + static String findParentHost(String nodeName) { + if (expectedNodeName1.matcher(nodeName).matches()) { + return replaceNodeName1.matcher(nodeName).replaceFirst("."); + } + if (expectedNodeName2.matcher(nodeName).matches()) { + return replaceNodeName2.matcher(nodeName).replaceFirst("."); + } + return null; + } private void addTls(JsonGenerator g) throws java.io.IOException { g.writeFieldName("tls"); g.writeStartObject(); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGeneratorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGeneratorTest.java index c24fcb27dc9..efe9cfe5060 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGeneratorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGeneratorTest.java @@ -31,7 +31,7 @@ public class OpenTelemetryConfigGeneratorTest { void testBuildsYaml() { var mockZone = new Zone(SystemName.PublicCd, Environment.prod, RegionName.from("mock")); var app = ApplicationId.from("mytenant", "myapp", "myinstance"); - var generator = new OpenTelemetryConfigGenerator(mockZone, app); + var generator = new OpenTelemetryConfigGenerator(mockZone, app, true); var root = new MockRoot(); var mockHost = new Host(root, "localhost2.local"); @@ -50,12 +50,15 @@ public class OpenTelemetryConfigGeneratorTest { var mockSvc2 = new MockService(root, "searchnode"); mockSvc2.setProp("clustername", "mycluster"); mockSvc2.setProp("clustertype", "mockup"); - var mockPort2 = new StatePortInfo("other.host.local", 19102, mockSvc2); + var mockPort2 = new StatePortInfo("other123x.host.local", 19102, mockSvc2); generator.addStatePorts(List.of(mockPort1, mockPort2)); String yaml = generator.generate(); // System.err.println(">>>\n" + yaml + "\n<<<"); assertTrue(yaml.contains("sentinel")); + String want = """ + "parentHostname":"other123.host.local"""; + assertTrue(yaml.contains(want)); } static class MockService extends AbstractService { @@ -70,4 +73,22 @@ public class OpenTelemetryConfigGeneratorTest { @Override public void allocatePorts(int start, PortAllocBridge from) { } } + @Test + void testFindParentHost() { + String result; + result = OpenTelemetryConfigGenerator.findParentHost("n1234c.foo.bar.some.cloud"); + assertEquals("n1234.foo.bar.some.cloud", result); + result = OpenTelemetryConfigGenerator.findParentHost("n1234-v6-7.foo.bar.some.cloud"); + assertEquals("n1234.foo.bar.some.cloud", result); + result = OpenTelemetryConfigGenerator.findParentHost("2000a.foo.bar.some.cloud"); + assertEquals("2000.foo.bar.some.cloud", result); + result = OpenTelemetryConfigGenerator.findParentHost("2000-v6-10.foo.bar.some.cloud"); + assertEquals("2000.foo.bar.some.cloud", result); + result = OpenTelemetryConfigGenerator.findParentHost("foobar.some.cloud"); + assertNull(result); + result = OpenTelemetryConfigGenerator.findParentHost("foo123bar.some.cloud"); + assertNull(result); + result = OpenTelemetryConfigGenerator.findParentHost("foo123.some.cloud"); + assertNull(result); + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java index 71e6d9e013d..c8de850ed98 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java @@ -47,7 +47,7 @@ public abstract class ConfigServerMaintainer extends Maintainer { @Override public void completed(String job, double successFactorDeviation, long durationMs) { - var context = metric.createContext(Map.of("job", job)); + var context = metric.createContext(Map.of("maintainer", job)); metric.set("maintenance.successFactorDeviation", successFactorDeviation, context); metric.set("maintenance.duration", durationMs, context); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java index c7be505acaa..678e4aa5781 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java @@ -58,7 +58,7 @@ public abstract class NodeRepositoryMaintainer extends Maintainer { @Override public void completed(String job, double successFactor, long duration) { - var context = metric.createContext(Map.of("job", job)); + var context = metric.createContext(Map.of("maintainer", job)); metric.set("maintenance.successFactorDeviation", successFactor, context); metric.set("maintenance.duration", duration, context); } diff --git a/parent/pom.xml b/parent/pom.xml index acd551817b1..9eb5c28ed05 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -66,6 +66,10 @@ <version>${maven-bundle-plugin.vespa.version}</version> <configuration> <obrRepository>NONE</obrRepository> + <!-- Hide warnings for multi-release jars, e.g. bouncycastle --> + <instructions> + <_fixupmessages>"Classes found in the wrong directory"</_fixupmessages> + </instructions> </configuration> </plugin> <plugin> diff --git a/searchlib/src/apps/vespa-query-analyzer/vespa-query-analyzer.cpp b/searchlib/src/apps/vespa-query-analyzer/vespa-query-analyzer.cpp index 16722a7e135..ea7dd673aa0 100644 --- a/searchlib/src/apps/vespa-query-analyzer/vespa-query-analyzer.cpp +++ b/searchlib/src/apps/vespa-query-analyzer/vespa-query-analyzer.cpp @@ -40,19 +40,12 @@ int rel_diff(double a, double b, double e, double m) { } void apply_diff(vespalib::string &str, int diff, char small, char big, int len) { - if (diff < len) { - for (int i = 0; i < diff; ++i) { + for (int i = 0; i < diff && i < len; ++i) { + if (diff + i >= len * 2) { + str.append(big); + } else { str.append(small); } - } else { - diff -= len; - for (int i = 0; i < len; ++i) { - if (diff > i) { - str.append(big); - } else { - str.append(small); - } - } } } @@ -311,6 +304,8 @@ struct Node { vespalib::string type = "unknown"; uint32_t id = 0; uint32_t docid_limit = 0; + vespalib::string field_name; + vespalib::string query_term; bool strict = false; FlowStats flow_stats = FlowStats(0.0, 0.0, 0.0); size_t count = 0; @@ -328,6 +323,26 @@ struct Node { type = strip_name(type); id = obj["id"].asLong(); docid_limit = obj["docid_limit"].asLong(); + query_term = obj["query_term"].asString().make_stringref(); + if (query_term.size() > 0) { + const Inspector &attr = obj["attribute"]; + if (attr.valid()) { + field_name = attr["name"].asString().make_stringref(); + if (type == "AttributeFieldBlueprint") { + type = fmt("Attribute{%s,%s}", + attr["type"].asString().make_string().c_str(), + attr["fast_search"].asBool() ? "fs" : "lookup"); + } + } else { + field_name = obj["field_name"].asString().make_stringref(); + if (type == "DiskTermBlueprint") { + type = "DiskTerm"; + } + if (type == "MemoryTermBlueprint") { + type = "MemoryTerm"; + } + } + } strict = obj["strict"].asBool(); flow_stats.estimate = obj["relative_estimate"].asDouble(); flow_stats.cost = obj["cost"].asDouble(); @@ -344,11 +359,18 @@ struct Node { } ~Node(); vespalib::string name() const { + vespalib::string res = type; if (id > 0) { - return fmt("%s[%u]", type.c_str(), id); - } else { - return fmt("%s", type.c_str()); + res.append(fmt("[%u]", id)); } + if (query_term.size() > 0) { + if (field_name.size() > 0) { + res.append(fmt(" %s:%s", field_name.c_str(), query_term.c_str())); + } else { + res.append(fmt(" %s", query_term.c_str())); + } + } + return res; } double rel_count() const { return double(count) / docid_limit; diff --git a/streamingvisitors/src/tests/document/CMakeLists.txt b/streamingvisitors/src/tests/document/CMakeLists.txt index 717626a9492..4eaa685fc84 100644 --- a/streamingvisitors/src/tests/document/CMakeLists.txt +++ b/streamingvisitors/src/tests/document/CMakeLists.txt @@ -4,5 +4,6 @@ vespa_add_executable(vsm_document_test_app TEST document_test.cpp DEPENDS streamingvisitors + GTest::gtest ) vespa_add_test(NAME vsm_document_test_app COMMAND vsm_document_test_app) diff --git a/streamingvisitors/src/tests/document/document_test.cpp b/streamingvisitors/src/tests/document/document_test.cpp index 9d35df80c73..8a2f8614b58 100644 --- a/streamingvisitors/src/tests/document/document_test.cpp +++ b/streamingvisitors/src/tests/document/document_test.cpp @@ -1,26 +1,16 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/vespalib/testkit/testapp.h> #include <vespa/document/fieldvalue/fieldvalues.h> #include <vespa/document/datatype/documenttype.h> #include <vespa/vsm/common/storagedocument.h> +#include <vespa/vespalib/gtest/gtest.h> #include <vespa/vespalib/stllike/asciistream.h> using namespace document; namespace vsm { -class DocumentTest : public vespalib::TestApp -{ -private: - void testStorageDocument(); - void testStringFieldIdTMap(); -public: - int Main() override; -}; - -void -DocumentTest::testStorageDocument() +TEST(DocumentTest, storage_document) { DocumentType dt("testdoc", 0); @@ -46,84 +36,72 @@ DocumentTest::testStorageDocument() StorageDocument sdoc(std::move(doc), fpmap, 3); ASSERT_TRUE(sdoc.valid()); - EXPECT_EQUAL(std::string("foo"), sdoc.getField(0)->getAsString()); - EXPECT_EQUAL(std::string("bar"), sdoc.getField(1)->getAsString()); + EXPECT_EQ("foo", sdoc.getField(0)->getAsString()); + EXPECT_EQ("bar", sdoc.getField(1)->getAsString()); EXPECT_TRUE(sdoc.getField(2) == nullptr); // test caching - EXPECT_EQUAL(std::string("foo"), sdoc.getField(0)->getAsString()); - EXPECT_EQUAL(std::string("bar"), sdoc.getField(1)->getAsString()); + EXPECT_EQ("foo", sdoc.getField(0)->getAsString()); + EXPECT_EQ("bar", sdoc.getField(1)->getAsString()); EXPECT_TRUE(sdoc.getField(2) == nullptr); // set new values EXPECT_TRUE(sdoc.setField(0, FieldValue::UP(new StringFieldValue("baz")))); - EXPECT_EQUAL(std::string("baz"), sdoc.getField(0)->getAsString()); - EXPECT_EQUAL(std::string("bar"), sdoc.getField(1)->getAsString()); + EXPECT_EQ("baz", sdoc.getField(0)->getAsString()); + EXPECT_EQ("bar", sdoc.getField(1)->getAsString()); EXPECT_TRUE(sdoc.getField(2) == nullptr); EXPECT_TRUE(sdoc.setField(1, FieldValue::UP(new StringFieldValue("qux")))); - EXPECT_EQUAL(std::string("baz"), sdoc.getField(0)->getAsString()); - EXPECT_EQUAL(std::string("qux"), sdoc.getField(1)->getAsString()); + EXPECT_EQ("baz", sdoc.getField(0)->getAsString()); + EXPECT_EQ("qux", sdoc.getField(1)->getAsString()); EXPECT_TRUE(sdoc.getField(2) == nullptr); EXPECT_TRUE(sdoc.setField(2, FieldValue::UP(new StringFieldValue("quux")))); - EXPECT_EQUAL(std::string("baz"), sdoc.getField(0)->getAsString()); - EXPECT_EQUAL(std::string("qux"), sdoc.getField(1)->getAsString()); - EXPECT_EQUAL(std::string("quux"), sdoc.getField(2)->getAsString()); + EXPECT_EQ("baz", sdoc.getField(0)->getAsString()); + EXPECT_EQ("qux", sdoc.getField(1)->getAsString()); + EXPECT_EQ("quux", sdoc.getField(2)->getAsString()); EXPECT_TRUE(!sdoc.setField(3, FieldValue::UP(new StringFieldValue("thud")))); SharedFieldPathMap fim; StorageDocument s2(std::make_unique<document::Document>(), fim, 0); - EXPECT_EQUAL(IdString().toString(), s2.docDoc().getId().toString()); + EXPECT_EQ(IdString().toString(), s2.docDoc().getId().toString()); } -void DocumentTest::testStringFieldIdTMap() +TEST(DocumentTest, string_field_id_t_map) { StringFieldIdTMap m; - EXPECT_EQUAL(0u, m.highestFieldNo()); + EXPECT_EQ(0u, m.highestFieldNo()); EXPECT_TRUE(StringFieldIdTMap::npos == m.fieldNo("unknown")); m.add("f1"); - EXPECT_EQUAL(0u, m.fieldNo("f1")); - EXPECT_EQUAL(1u, m.highestFieldNo()); + EXPECT_EQ(0u, m.fieldNo("f1")); + EXPECT_EQ(1u, m.highestFieldNo()); m.add("f1"); - EXPECT_EQUAL(0u, m.fieldNo("f1")); - EXPECT_EQUAL(1u, m.highestFieldNo()); + EXPECT_EQ(0u, m.fieldNo("f1")); + EXPECT_EQ(1u, m.highestFieldNo()); m.add("f2"); - EXPECT_EQUAL(1u, m.fieldNo("f2")); - EXPECT_EQUAL(2u, m.highestFieldNo()); + EXPECT_EQ(1u, m.fieldNo("f2")); + EXPECT_EQ(2u, m.highestFieldNo()); m.add("f3", 7); - EXPECT_EQUAL(7u, m.fieldNo("f3")); - EXPECT_EQUAL(8u, m.highestFieldNo()); + EXPECT_EQ(7u, m.fieldNo("f3")); + EXPECT_EQ(8u, m.highestFieldNo()); m.add("f3"); - EXPECT_EQUAL(7u, m.fieldNo("f3")); - EXPECT_EQUAL(8u, m.highestFieldNo()); + EXPECT_EQ(7u, m.fieldNo("f3")); + EXPECT_EQ(8u, m.highestFieldNo()); m.add("f2", 13); - EXPECT_EQUAL(13u, m.fieldNo("f2")); - EXPECT_EQUAL(14u, m.highestFieldNo()); + EXPECT_EQ(13u, m.fieldNo("f2")); + EXPECT_EQ(14u, m.highestFieldNo()); m.add("f4"); - EXPECT_EQUAL(3u, m.fieldNo("f4")); - EXPECT_EQUAL(14u, m.highestFieldNo()); + EXPECT_EQ(3u, m.fieldNo("f4")); + EXPECT_EQ(14u, m.highestFieldNo()); { vespalib::asciistream os; StringFieldIdTMap t; t.add("b"); t.add("a"); os << t; - EXPECT_EQUAL(vespalib::string("a = 1\nb = 0\n"), os.str()); + EXPECT_EQ(vespalib::string("a = 1\nb = 0\n"), os.str()); } } -int -DocumentTest::Main() -{ - TEST_INIT("document_test"); - - testStorageDocument(); - testStringFieldIdTMap(); - - TEST_DONE(); -} - } -TEST_APPHOOK(vsm::DocumentTest); - +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/streamingvisitors/src/tests/querywrapper/CMakeLists.txt b/streamingvisitors/src/tests/querywrapper/CMakeLists.txt index 9fa9f75f047..1acd513f952 100644 --- a/streamingvisitors/src/tests/querywrapper/CMakeLists.txt +++ b/streamingvisitors/src/tests/querywrapper/CMakeLists.txt @@ -4,5 +4,6 @@ vespa_add_executable(streamingvisitors_querywrapper_test_app TEST querywrapper_test.cpp DEPENDS streamingvisitors + GTest::gtest ) vespa_add_test(NAME streamingvisitors_querywrapper_test_app COMMAND streamingvisitors_querywrapper_test_app) diff --git a/streamingvisitors/src/tests/querywrapper/querywrapper_test.cpp b/streamingvisitors/src/tests/querywrapper/querywrapper_test.cpp index 2a4b9e1f869..6deb0d4cda4 100644 --- a/streamingvisitors/src/tests/querywrapper/querywrapper_test.cpp +++ b/streamingvisitors/src/tests/querywrapper/querywrapper_test.cpp @@ -1,10 +1,10 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/vespalib/testkit/testapp.h> #include <vespa/searchlib/query/tree/querybuilder.h> #include <vespa/searchlib/query/tree/simplequery.h> #include <vespa/searchlib/query/tree/stackdumpcreator.h> #include <vespa/searchvisitor/querywrapper.h> +#include <vespa/vespalib/gtest/gtest.h> #include <iostream> using namespace search; @@ -13,17 +13,7 @@ using namespace search::streaming; namespace streaming { -class QueryWrapperTest : public vespalib::TestApp -{ -private: - void testQueryWrapper(); - -public: - int Main() override; -}; - -void -QueryWrapperTest::testQueryWrapper() +TEST(QueryWrapperTest, test_query_wrapper) { QueryNodeResultFactory empty; { @@ -49,27 +39,17 @@ QueryWrapperTest::testQueryWrapper() q.getLeaves(terms); ASSERT_TRUE(tl.size() == 3 && terms.size() == 3); for (size_t i = 0; i < 3; ++i) { - EXPECT_EQUAL(tl[i], terms[i]); + EXPECT_EQ(tl[i], terms[i]); std::cout << "t[" << i << "]:" << terms[i] << std::endl; auto phrase = dynamic_cast<PhraseQueryNode*>(terms[i]); - EXPECT_EQUAL(i == 1, phrase != nullptr); + EXPECT_EQ(i == 1, phrase != nullptr); if (i == 1) { - EXPECT_EQUAL(3u, phrase->get_terms().size()); + EXPECT_EQ(3u, phrase->get_terms().size()); } } } } -int -QueryWrapperTest::Main() -{ - TEST_INIT("querywrapper_test"); - - testQueryWrapper(); - - TEST_DONE(); } -} // namespace streaming - -TEST_APPHOOK(::streaming::QueryWrapperTest) +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/streamingvisitors/src/tests/textutil/CMakeLists.txt b/streamingvisitors/src/tests/textutil/CMakeLists.txt index da99850e43e..877664ddfd6 100644 --- a/streamingvisitors/src/tests/textutil/CMakeLists.txt +++ b/streamingvisitors/src/tests/textutil/CMakeLists.txt @@ -4,5 +4,6 @@ vespa_add_executable(vsm_textutil_test_app TEST textutil_test.cpp DEPENDS streamingvisitors + GTest::gtest ) vespa_add_test(NAME vsm_textutil_test_app COMMAND vsm_textutil_test_app) diff --git a/streamingvisitors/src/tests/textutil/textutil_test.cpp b/streamingvisitors/src/tests/textutil/textutil_test.cpp index f7f340a2182..1dbabf1f0af 100644 --- a/streamingvisitors/src/tests/textutil/textutil_test.cpp +++ b/streamingvisitors/src/tests/textutil/textutil_test.cpp @@ -1,7 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/vespalib/testkit/testapp.h> #include <vespa/fastlib/text/normwordfolder.h> +#include <vespa/vespalib/gtest/gtest.h> #include <vespa/vsm/searcher/fold.h> #include <vespa/vsm/searcher/futf8strchrfieldsearcher.h> #include <vespa/vsm/searcher/utf8stringfieldsearcherbase.h> @@ -23,9 +23,9 @@ using SizeV = Vector<size_t>; using SFSB = UTF8StringFieldSearcherBase; using FSFS = FUTF8StrChrFieldSearcher; -class TextUtilTest : public vespalib::TestApp +class TextUtilTest : public ::testing::Test { -private: +protected: ucs4_t getUTF8Char(const char * src); template <typename BW, bool OFF> void assertSkipSeparators(const char * input, size_t len, const UCS4V & expdstbuf, const SizeV & expoffsets); @@ -38,23 +38,23 @@ private: template <typename BW, bool OFF> void testSkipSeparators(); - void testSkipSeparators(); - void testSeparatorCharacter(); - void testAnsiFold(); - void test_lfoldua(); -#ifdef __x86_64__ - void test_sse2_foldua(); -#endif -public: - int Main() override; + TextUtilTest(); + ~TextUtilTest() override; }; +TextUtilTest::TextUtilTest() + : ::testing::Test() +{ +} + +TextUtilTest::~TextUtilTest() = default; + ucs4_t TextUtilTest::getUTF8Char(const char * src) { ucs4_t retval = Fast_UnicodeUtil::GetUTF8Char(src); - ASSERT_TRUE(retval != Fast_UnicodeUtil::_BadUTF8Char); + EXPECT_TRUE(retval != Fast_UnicodeUtil::_BadUTF8Char); return retval; } @@ -68,12 +68,12 @@ TextUtilTest::assertSkipSeparators(const char * input, size_t len, const UCS4V & UTF8StrChrFieldSearcher fs(0); BW bw(dstbuf.get(), offsets.get()); size_t dstlen = fs.skipSeparators(srcbuf, len, bw); - EXPECT_EQUAL(dstlen, expdstbuf.size()); + EXPECT_EQ(dstlen, expdstbuf.size()); ASSERT_TRUE(dstlen == expdstbuf.size()); for (size_t i = 0; i < dstlen; ++i) { - EXPECT_EQUAL(dstbuf[i], expdstbuf[i]); + EXPECT_EQ(dstbuf[i], expdstbuf[i]); if (OFF) { - EXPECT_EQUAL(offsets[i], expoffsets[i]); + EXPECT_EQ(offsets[i], expoffsets[i]); } } } @@ -83,7 +83,7 @@ TextUtilTest::assertAnsiFold(const std::string & toFold, const std::string & exp { char folded[256]; EXPECT_TRUE(FSFS::ansiFold(toFold.c_str(), toFold.size(), folded)); - EXPECT_EQUAL(std::string(folded, toFold.size()), exp); + EXPECT_EQ(std::string(folded, toFold.size()), exp); } void @@ -91,7 +91,7 @@ TextUtilTest::assertAnsiFold(char c, char exp) { char folded; EXPECT_TRUE(FSFS::ansiFold(&c, 1, &folded)); - EXPECT_EQUAL((int32_t)folded, (int32_t)exp); + EXPECT_EQ((int32_t)folded, (int32_t)exp); } #ifdef __x86_64__ @@ -103,8 +103,8 @@ TextUtilTest::assert_sse2_foldua(const std::string & toFold, size_t charFolded, const unsigned char * toFoldOrg = reinterpret_cast<const unsigned char *>(toFold.c_str()); const unsigned char * retval = sse2_foldua(toFoldOrg, toFold.size(), reinterpret_cast<unsigned char *>(folded + alignedStart)); - EXPECT_EQUAL((size_t)(retval - toFoldOrg), charFolded); - EXPECT_EQUAL(std::string(folded + alignedStart, charFolded), exp); + EXPECT_EQ((size_t)(retval - toFoldOrg), charFolded); + EXPECT_EQ(std::string(folded + alignedStart, charFolded), exp); } void @@ -115,9 +115,9 @@ TextUtilTest::assert_sse2_foldua(unsigned char c, unsigned char exp, size_t char unsigned char folded[32]; size_t alignedStart = 0xF - (size_t(folded + 0xF) % 0x10); const unsigned char * retval = sse2_foldua(toFold, 16, folded + alignedStart); - EXPECT_EQUAL((size_t)(retval - toFold), charFolded); + EXPECT_EQ((size_t)(retval - toFold), charFolded); for (size_t i = 0; i < charFolded; ++i) { - EXPECT_EQUAL((int32_t)folded[i + alignedStart], (int32_t)exp); + EXPECT_EQ((int32_t)folded[i + alignedStart], (int32_t)exp); } } #endif @@ -145,8 +145,7 @@ TextUtilTest::testSkipSeparators() SizeV().a(0).a(0).a(2).a(3).a(3)); } -void -TextUtilTest::testSkipSeparators() +TEST_F(TextUtilTest, skip_separators) { Fast_NormalizeWordFolder::Setup(Fast_NormalizeWordFolder::DO_SHARP_S_SUBSTITUTION); @@ -154,8 +153,7 @@ TextUtilTest::testSkipSeparators() testSkipSeparators<SFSB::OffsetWrapper, true>(); } -void -TextUtilTest::testSeparatorCharacter() +TEST_F(TextUtilTest, separator_character) { EXPECT_TRUE(SFSB::isSeparatorCharacter('\x00')); EXPECT_TRUE(SFSB::isSeparatorCharacter('\x01')); @@ -194,8 +192,7 @@ TextUtilTest::testSeparatorCharacter() EXPECT_TRUE(! SFSB::isSeparatorCharacter('\x20')); // space } -void -TextUtilTest::testAnsiFold() +TEST_F(TextUtilTest, ansi_fold) { FieldSearcher::init(); assertAnsiFold("", ""); @@ -220,8 +217,7 @@ TextUtilTest::testAnsiFold() } } -void -TextUtilTest::test_lfoldua() +TEST_F(TextUtilTest, lfoldua) { FieldSearcher::init(); char folded[256]; @@ -229,12 +225,11 @@ TextUtilTest::test_lfoldua() const char * toFold = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; size_t len = strlen(toFold); EXPECT_TRUE(FSFS::lfoldua(toFold, len, folded, alignedStart)); - EXPECT_EQUAL(std::string(folded + alignedStart, len), "abcdefghijklmnopqrstuvwxyz"); + EXPECT_EQ(std::string(folded + alignedStart, len), "abcdefghijklmnopqrstuvwxyz"); } #ifdef __x86_64__ -void -TextUtilTest::test_sse2_foldua() +TEST_F(TextUtilTest, sse2_foldua) { assert_sse2_foldua("", 0, ""); assert_sse2_foldua("ABCD", 0, ""); @@ -263,22 +258,6 @@ TextUtilTest::test_sse2_foldua() } #endif -int -TextUtilTest::Main() -{ - TEST_INIT("textutil_test"); - - testSkipSeparators(); - testSeparatorCharacter(); - testAnsiFold(); - test_lfoldua(); -#ifdef __x86_64__ - test_sse2_foldua(); -#endif - - TEST_DONE(); -} - } -TEST_APPHOOK(vsm::TextUtilTest); +GTEST_MAIN_RUN_ALL_TESTS() |