acc: Implement config merge (#2294)

## Changes
Instead of using leaf-most config, all configs from root at
acceptance/test.toml to all intermediate ones to leaf config are merged
into one. Maps are merged, slices are appended, other values are
overridden.

I had to disable caching, because it is tricky when merging is involved
- deep copy is needed. There is performance
impact but currently it is tiny, about 1%.

Also, remove empty root config.

## Tests

Manually checked that inheritance of LocalOnly setting worked for these
tests:

Before - integration tests showed:

```
PASS acceptance.TestAccept/bundle/templates/wrong-url (0.70s)
PASS acceptance.TestAccept/bundle/templates/wrong-path (0.44s)
```

After:

```
SKIP acceptance.TestAccept/bundle/templates/wrong-url (0.00s)
SKIP acceptance.TestAccept/bundle/templates/wrong-path (0.00s)
      acceptance_test.go:216: Disabled via LocalOnly setting in bundle/templates/test.toml, bundle/templates/wrong-path/test.toml (CLOUD_ENV=***)
```
This commit is contained in:
Denis Bilenko 2025-02-07 17:38:27 +01:00 committed by GitHub
parent f71583fbc0
commit ff4a5c2269
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 33 additions and 35 deletions

5
NOTICE
View File

@ -109,3 +109,8 @@ License - https://github.com/hexops/gotextdiff/blob/main/LICENSE
https://github.com/BurntSushi/toml https://github.com/BurntSushi/toml
Copyright (c) 2013 TOML authors Copyright (c) 2013 TOML authors
https://github.com/BurntSushi/toml/blob/master/COPYING https://github.com/BurntSushi/toml/blob/master/COPYING
dario.cat/mergo
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
https://github.com/darccio/mergo/blob/master/LICENSE

View File

@ -3,9 +3,11 @@ package acceptance_test
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"sync" "slices"
"strings"
"testing" "testing"
"dario.cat/mergo"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/databricks/cli/libs/testdiff" "github.com/databricks/cli/libs/testdiff"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -13,11 +15,6 @@ import (
const configFilename = "test.toml" const configFilename = "test.toml"
var (
configCache map[string]TestConfig
configMutex sync.Mutex
)
type TestConfig struct { type TestConfig struct {
// Place to describe what's wrong with this test. Does not affect how the test is run. // Place to describe what's wrong with this test. Does not affect how the test is run.
Badness string Badness string
@ -65,58 +62,55 @@ type ServerStub struct {
} }
} }
// FindConfig finds the closest config file. // FindConfigs finds all the config relevant for this test,
func FindConfig(t *testing.T, dir string) (string, bool) { // ordered from the most outermost (at acceptance/) to current test directory (identified by dir).
shared := false // 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{}
for { for {
path := filepath.Join(dir, configFilename) path := filepath.Join(dir, configFilename)
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil { if err == nil {
return path, shared configs = append(configs, path)
} }
shared = true
if dir == "" || dir == "." { if dir == "" || dir == "." {
break break
} }
if os.IsNotExist(err) {
dir = filepath.Dir(dir) dir = filepath.Dir(dir)
if err == nil || os.IsNotExist(err) {
continue continue
} }
t.Fatalf("Error while reading %s: %s", path, err) t.Fatalf("Error while reading %s: %s", path, err)
} }
t.Fatal("Config not found: " + configFilename) slices.Reverse(configs)
return "", shared return configs
} }
// LoadConfig loads the config file. Non-leaf configs are cached. // LoadConfig loads the config file. Non-leaf configs are cached.
func LoadConfig(t *testing.T, dir string) (TestConfig, string) { func LoadConfig(t *testing.T, dir string) (TestConfig, string) {
path, leafConfig := FindConfig(t, dir) configs := FindConfigs(t, dir)
if leafConfig { if len(configs) == 0 {
return DoLoadConfig(t, path), path return TestConfig{}, "(no config)"
} }
configMutex.Lock() result := DoLoadConfig(t, configs[0])
defer configMutex.Unlock()
if configCache == nil { for _, cfgName := range configs[1:] {
configCache = make(map[string]TestConfig) cfg := DoLoadConfig(t, cfgName)
err := mergo.Merge(&result, cfg, mergo.WithOverride, mergo.WithAppendSlice)
if err != nil {
t.Fatalf("Error during config merge: %s: %s", cfgName, err)
}
} }
result, ok := configCache[path] return result, strings.Join(configs, ", ")
if ok {
return result, path
}
result = DoLoadConfig(t, path)
configCache[path] = result
return result, path
} }
func DoLoadConfig(t *testing.T, path string) TestConfig { func DoLoadConfig(t *testing.T, path string) TestConfig {

View File

@ -1,2 +0,0 @@
# If test directory nor any of its parents do not have test.toml then this file serves as fallback configuration.
# The configurations are not merged across parents; the closest one is used fully.

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.23
toolchain go1.23.4 toolchain go1.23.4
require ( require (
dario.cat/mergo v1.0.1 // BSD 3-Clause
github.com/BurntSushi/toml v1.4.0 // MIT github.com/BurntSushi/toml v1.4.0 // MIT
github.com/Masterminds/semver/v3 v3.3.1 // MIT github.com/Masterminds/semver/v3 v3.3.1 // MIT
github.com/briandowns/spinner v1.23.1 // Apache 2.0 github.com/briandowns/spinner v1.23.1 // Apache 2.0

4
go.sum generated
View File

@ -5,8 +5,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKF
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=