Files
ConfigKit/independence_test.go

117 lines
3.0 KiB
Go

package configkit
import (
"bytes"
"errors"
"fmt"
"strings"
"sync"
"testing"
)
type independentText string
func (value independentText) MarshalText() ([]byte, error) {
return []byte(value), nil
}
func (value *independentText) UnmarshalText(text []byte) error {
*value = independentText(text)
return nil
}
type independentConfig struct {
Labels map[string]string `yaml:"labels"`
Items []string `yaml:"items"`
Custom independentText `yaml:"custom"`
}
func TestParallelLoadsShareRulesButNotMutableValues(t *testing.T) {
definition := Definition[independentConfig]{
Defaults: func() independentConfig {
return independentConfig{
Labels: map[string]string{"default": "kept"},
Items: []string{"default"},
Custom: "default",
}
},
}
const count = 32
results := make([]*Result[independentConfig], count)
errorsByIndex := make([]error, count)
var group sync.WaitGroup
for index := range count {
group.Add(1)
go func(index int) {
defer group.Done()
input := fmt.Sprintf("custom: value-%d\n", index)
results[index], errorsByIndex[index] = Load(Bytes("parallel.yml", []byte(input)), Options[independentConfig]{Definition: definition})
}(index)
}
group.Wait()
for index, err := range errorsByIndex {
if err != nil {
t.Fatalf("Load(%d) failed: %v", index, err)
}
}
results[0].Config.Labels["default"] = "mutated"
results[0].Config.Items[0] = "mutated"
results[0].Config.Custom = "mutated"
for index := 1; index < count; index++ {
if results[index].Config.Labels["default"] != "kept" || results[index].Config.Items[0] != "default" || results[index].Config.Custom != independentText(fmt.Sprintf("value-%d", index)) {
t.Fatalf("result %d shares mutable state: %#v", index, results[index].Config)
}
}
var effective bytes.Buffer
if err := results[0].WriteEffective(&effective); err != nil {
t.Fatal(err)
}
if strings.Contains(effective.String(), "mutated") || !strings.Contains(effective.String(), "default: kept") || !strings.Contains(effective.String(), "value-0") {
t.Fatalf("frozen effective snapshot = %q", effective.String())
}
}
func TestConcurrentLoadsOfCopiedReaderSourceHaveOneClaim(t *testing.T) {
source := Reader("shared", strings.NewReader("value: one\n"))
copied := source
type config struct {
Value string `yaml:"value"`
}
start := make(chan struct{})
errorsFound := make(chan error, 2)
var group sync.WaitGroup
for _, candidate := range []Source{source, copied} {
group.Add(1)
go func(candidate Source) {
defer group.Done()
<-start
_, err := Load[config](candidate)
errorsFound <- err
}(candidate)
}
close(start)
group.Wait()
close(errorsFound)
successes, used := 0, 0
for err := range errorsFound {
if err == nil {
successes++
continue
}
var loadError *LoadError
if errors.As(err, &loadError) {
diagnostics := loadError.Diagnostics()
if len(diagnostics) == 1 && diagnostics[0].Code == CodeSourceReaderUsed {
used++
}
}
}
if successes != 1 || used != 1 {
t.Fatalf("successes=%d reader-used=%d", successes, used)
}
}