2025-01-24 14:28:23 +00:00
|
|
|
package acceptance_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2025-02-07 16:38:27 +00:00
|
|
|
"slices"
|
|
|
|
"strings"
|
2025-01-24 14:28:23 +00:00
|
|
|
"testing"
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
"dario.cat/mergo"
|
2025-01-24 14:28:23 +00:00
|
|
|
"github.com/BurntSushi/toml"
|
2025-01-27 09:11:06 +00:00
|
|
|
"github.com/databricks/cli/libs/testdiff"
|
2025-02-12 13:00:57 +00:00
|
|
|
"github.com/databricks/cli/libs/testserver"
|
2025-02-28 14:23:50 +00:00
|
|
|
ignore "github.com/sabhiram/go-gitignore"
|
2025-01-24 14:28:23 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
const configFilename = "test.toml"
|
|
|
|
|
|
|
|
type TestConfig struct {
|
|
|
|
// Place to describe what's wrong with this test. Does not affect how the test is run.
|
2025-02-25 08:53:46 +00:00
|
|
|
Badness *string
|
2025-01-24 14:28:23 +00:00
|
|
|
|
|
|
|
// Which OSes the test is enabled on. Each string is compared against runtime.GOOS.
|
|
|
|
// If absent, default to true.
|
|
|
|
GOOS map[string]bool
|
2025-01-27 09:11:06 +00:00
|
|
|
|
2025-02-26 16:01:49 +00:00
|
|
|
// If true, run this test when running locally with a testserver
|
|
|
|
Local *bool
|
|
|
|
|
|
|
|
// If true, run this test when running with cloud env configured
|
|
|
|
Cloud *bool
|
2025-02-03 10:43:25 +00:00
|
|
|
|
2025-03-11 15:48:08 +00:00
|
|
|
// If true, run this test when running with cloud env configured and -short is not passed
|
|
|
|
// This also sets -tail when -v is passed.
|
|
|
|
CloudSlow *bool
|
|
|
|
|
2025-03-07 16:41:25 +00:00
|
|
|
// If true and Cloud=true, run this test only if unity catalog is available in the cloud environment
|
|
|
|
RequiresUnityCatalog *bool
|
|
|
|
|
2025-01-27 09:11:06 +00:00
|
|
|
// List of additional replacements to apply on this test.
|
|
|
|
// Old is a regexp, New is a replacement expression.
|
|
|
|
Repls []testdiff.Replacement
|
2025-01-30 10:43:07 +00:00
|
|
|
|
|
|
|
// List of server stubs to load. Example configuration:
|
|
|
|
//
|
|
|
|
// [[Server]]
|
|
|
|
// Pattern = "POST /api/2.1/jobs/create"
|
|
|
|
// Response.Body = '''
|
|
|
|
// {
|
|
|
|
// "job_id": 1111
|
|
|
|
// }
|
|
|
|
// '''
|
|
|
|
Server []ServerStub
|
2025-01-31 13:31:23 +00:00
|
|
|
|
|
|
|
// Record the requests made to the server and write them as output to
|
|
|
|
// out.requests.txt
|
2025-02-25 08:53:46 +00:00
|
|
|
RecordRequests *bool
|
2025-02-05 09:32:15 +00:00
|
|
|
|
|
|
|
// List of request headers to include when recording requests.
|
|
|
|
IncludeRequestHeaders []string
|
2025-02-28 14:23:50 +00:00
|
|
|
|
|
|
|
// List of gitignore patterns to ignore when checking output files
|
|
|
|
Ignore []string
|
|
|
|
|
|
|
|
CompiledIgnoreObject *ignore.GitIgnore
|
2025-01-30 10:43:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ServerStub struct {
|
|
|
|
// The HTTP method and path to match. Examples:
|
|
|
|
// 1. /api/2.0/clusters/list (matches all methods)
|
|
|
|
// 2. GET /api/2.0/clusters/list
|
|
|
|
Pattern string
|
|
|
|
|
|
|
|
// The response body to return.
|
2025-02-12 13:00:57 +00:00
|
|
|
Response testserver.Response
|
Add synchronous logger for telemetry (#2432)
## Changes
This PR adds a synchronous telemetry logger for the CLI with a max
timeout of 3 seconds. Due to the 3-second timeout configuration, this is
only meant to be used in long-running commands.
This is a short-term solution. Eventually, we'd like to transition to a
daemon process to upload the telemetry logs to amortise the costs of
configuring authentication and maintaining a warm pool of HTTP
connections, as well as a better UX for the end user.
Note that users can set the `DATABRICKS_CLI_DISABLE_TELEMETRY`
environment variable to disable telemetry collection.
## Why
To collect telemetry, which was previously inaccessible to us, and
answer questions like which templates customers like to use and which
DABs features would be safe to deprecate.
## Tests
Unit and acceptance tests.
Also manually verified that the telemetry upload works:
```
(artifact-playground) ➜ cli git:(sync-logger) cli selftest send-telemetry --debug
15:58:20 Info: start pid=40386 version=0.0.0-dev+a2825ca89a23 args="cli, selftest, send-telemetry, --debug"
15:58:20 Debug: Loading DEFAULT profile from /Users/shreyas.goenka/.databrickscfg pid=40386 sdk=true
15:58:20 Info: completed execution pid=40386 exit_code=0
15:58:21 Debug: POST /telemetry-ext
> {
> "items": null,
> "protoLogs": [
> "{\"frontend_log_event_id\":\"82d29b3a-d5ff-48f3-8a21-dae6e08d2999\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)",
> "{\"frontend_log_event_id\":\"d6be8220-7db8-45d9-97d6-4c09c25e2664\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)"
> ],
> "uploadTime": 1741186700967
> }
< HTTP/2.0 200 OK
< {
< "errors": null,
< "numProtoSuccess": 2,
< "numSuccess": 0
< } pid=40386 sdk=true
```
2025-03-12 13:05:10 +00:00
|
|
|
|
|
|
|
// Artificial delay in seconds to simulate slow responses.
|
|
|
|
DelaySeconds *float64
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
// FindConfigs finds all the config relevant for this test,
|
|
|
|
// ordered from the most outermost (at acceptance/) to current test directory (identified by dir).
|
|
|
|
// Argument dir must be a relative path from the root of acceptance tests (<project_root>/acceptance/).
|
|
|
|
func FindConfigs(t *testing.T, dir string) []string {
|
|
|
|
configs := []string{}
|
2025-01-24 14:28:23 +00:00
|
|
|
for {
|
|
|
|
path := filepath.Join(dir, configFilename)
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
|
|
|
|
if err == nil {
|
2025-02-07 16:38:27 +00:00
|
|
|
configs = append(configs, path)
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if dir == "" || dir == "." {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
dir = filepath.Dir(dir)
|
|
|
|
|
|
|
|
if err == nil || os.IsNotExist(err) {
|
2025-01-24 14:28:23 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Fatalf("Error while reading %s: %s", path, err)
|
|
|
|
}
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
slices.Reverse(configs)
|
|
|
|
return configs
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfig loads the config file. Non-leaf configs are cached.
|
|
|
|
func LoadConfig(t *testing.T, dir string) (TestConfig, string) {
|
2025-02-07 16:38:27 +00:00
|
|
|
configs := FindConfigs(t, dir)
|
2025-01-24 14:28:23 +00:00
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
if len(configs) == 0 {
|
|
|
|
return TestConfig{}, "(no config)"
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
result := DoLoadConfig(t, configs[0])
|
2025-01-24 14:28:23 +00:00
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
for _, cfgName := range configs[1:] {
|
|
|
|
cfg := DoLoadConfig(t, cfgName)
|
2025-02-25 08:53:46 +00:00
|
|
|
err := mergo.Merge(&result, cfg, mergo.WithOverride, mergo.WithoutDereference, mergo.WithAppendSlice)
|
2025-02-07 16:38:27 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error during config merge: %s: %s", cfgName, err)
|
|
|
|
}
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
2025-02-28 14:23:50 +00:00
|
|
|
result.CompiledIgnoreObject = ignore.CompileIgnoreLines(result.Ignore...)
|
|
|
|
|
2025-02-07 16:38:27 +00:00
|
|
|
return result, strings.Join(configs, ", ")
|
2025-01-24 14:28:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func DoLoadConfig(t *testing.T, path string) TestConfig {
|
|
|
|
bytes, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read config: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var config TestConfig
|
|
|
|
meta, err := toml.Decode(string(bytes), &config)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
keys := meta.Undecoded()
|
|
|
|
if len(keys) > 0 {
|
|
|
|
t.Fatalf("Undecoded keys in %s: %#v", path, keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
return config
|
|
|
|
}
|