Skip to content

Commit deb4860

Browse files
authored
Merge pull request #730 from LandonTClipp/issue_726
Fix config bug where mockery crashes when package map is nil
2 parents 726d76c + b648c23 commit deb4860

File tree

7 files changed

+239
-64
lines changed

7 files changed

+239
-64
lines changed

Taskfile.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ tasks:
5252

5353
test:
5454
cmds:
55-
- go test -v -coverprofile=coverage.txt ./...
55+
- go run gotest.tools/gotestsum --format testname -- -v -coverprofile=coverage.txt ./...
5656
desc: run unit tests
5757
sources:
5858
- "**/*.go"

cmd/showconfig.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ func showConfig(
3737
if err != nil {
3838
return stackerr.NewStackErrf(err, "failed to unmarshal config")
3939
}
40+
log, err := logging.GetLogger(config.LogLevel)
41+
if err != nil {
42+
return fmt.Errorf("getting logger: %w", err)
43+
}
44+
ctx = log.WithContext(ctx)
4045
if err := config.Initialize(ctx); err != nil {
4146
return err
4247
}
@@ -48,10 +53,7 @@ func showConfig(
4853
if err != nil {
4954
return stackerr.NewStackErrf(err, "failed to marshal yaml")
5055
}
51-
log, err := logging.GetLogger(config.LogLevel)
52-
if err != nil {
53-
panic(err)
54-
}
56+
5557
log.Info().Msgf("Using config: %s", config.Config)
5658

5759
fmt.Fprintf(outputter, "%s", string(out))

pkg/config/config.go

+114-37
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,14 @@ func (c *Config) GetPackages(ctx context.Context) ([]string, error) {
195195
return packageList, nil
196196
}
197197

198+
// getPackageConfigMap returns the map for the particular package, which includes
199+
// (but is not limited to) both the `configs` section and the `interfaces` section.
200+
// Note this does NOT return the `configs` section for the package. It returns the
201+
// entire mapping for the package.
198202
func (c *Config) getPackageConfigMap(ctx context.Context, packageName string) (map[string]any, error) {
203+
log := zerolog.Ctx(ctx)
204+
log.Trace().Msg("getting package config map")
205+
199206
cfgMap, err := c.CfgAsMap(ctx)
200207
if err != nil {
201208
return nil, err
@@ -207,13 +214,27 @@ func (c *Config) getPackageConfigMap(ctx context.Context, packageName string) (m
207214
}
208215
configAsMap, isMap := configUnmerged.(map[string]any)
209216
if isMap {
217+
log.Trace().Msg("package's value is a map, returning")
210218
return configAsMap, nil
211219
}
212-
return map[string]any{}, nil
220+
log.Trace().Msg("package's value is not a map")
221+
222+
// Package is something other than map, so set its value to an
223+
// empty map.
224+
emptyMap := map[string]any{}
225+
packageSection[packageName] = emptyMap
226+
return emptyMap, nil
213227

214228
}
229+
230+
// GetPackageConfig returns a struct representation of the package's config
231+
// as provided in yaml. If the package did not specify a config section,
232+
// this method will inject the top-level config into the package's config.
233+
// This is especially useful as it allows us to lazily evaluate a package's
234+
// config. If the package does specify config, this method takes care to merge
235+
// the top-level config with the values specified for this package.
215236
func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Config, error) {
216-
log := zerolog.Ctx(ctx)
237+
log := zerolog.Ctx(ctx).With().Str("package-path", packageName).Logger()
217238

218239
if c.pkgConfigCache == nil {
219240
log.Debug().Msg("package cache is nil")
@@ -223,11 +244,10 @@ func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Con
223244
return pkgConf, nil
224245
}
225246

226-
pkgConfig := reflect.New(reflect.ValueOf(c).Elem().Type()).Interface()
247+
pkgConfig := &Config{}
227248
if err := copier.Copy(pkgConfig, c); err != nil {
228249
return nil, fmt.Errorf("failed to copy config: %w", err)
229250
}
230-
pkgConfigTyped := pkgConfig.(*Config)
231251

232252
configMap, err := c.getPackageConfigMap(ctx, packageName)
233253
if err != nil {
@@ -237,18 +257,24 @@ func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Con
237257
configSection, ok := configMap["config"]
238258
if !ok {
239259
log.Debug().Msg("config section not provided for package")
240-
return pkgConfigTyped, nil
260+
configMap["config"] = map[string]any{}
261+
c.pkgConfigCache[packageName] = pkgConfig
262+
return pkgConfig, nil
241263
}
242264

243-
decoder, err := c.getDecoder(pkgConfigTyped)
265+
// We know that the package specified config that is overriding the top-level
266+
// config. We use a mapstructure decoder to decode the values in the yaml
267+
// into the pkgConfig struct. This has the effect of merging top-level
268+
// config with package-level config.
269+
decoder, err := c.getDecoder(pkgConfig)
244270
if err != nil {
245271
return nil, stackerr.NewStackErrf(err, "failed to get decoder")
246272
}
247273
if err := decoder.Decode(configSection); err != nil {
248274
return nil, err
249275
}
250-
c.pkgConfigCache[packageName] = pkgConfigTyped
251-
return pkgConfigTyped, nil
276+
c.pkgConfigCache[packageName] = pkgConfig
277+
return pkgConfig, nil
252278
}
253279

254280
func (c *Config) ExcludePath(path string) bool {
@@ -347,16 +373,15 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
347373
}
348374

349375
// Copy the package-level config to our interface-level config
350-
pkgConfigCopy := reflect.New(reflect.ValueOf(pkgConfig).Elem().Type()).Interface()
376+
pkgConfigCopy := &Config{}
351377
if err := copier.Copy(pkgConfigCopy, pkgConfig); err != nil {
352378
return nil, stackerr.NewStackErrf(err, "failed to create a copy of package config")
353379
}
354-
baseConfigTyped := pkgConfigCopy.(*Config)
355380

356381
interfaceSection, ok := interfacesSection[interfaceName]
357382
if !ok {
358383
log.Debug().Msg("interface not defined in package configuration")
359-
return []*Config{baseConfigTyped}, nil
384+
return []*Config{pkgConfigCopy}, nil
360385
}
361386

362387
interfaceSectionTyped, ok := interfaceSection.(map[string]any)
@@ -365,7 +390,7 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
365390
// the interface but not provide any additional config beyond what
366391
// is provided at the package level
367392
if reflect.ValueOf(&interfaceSection).Elem().IsZero() {
368-
return []*Config{baseConfigTyped}, nil
393+
return []*Config{pkgConfigCopy}, nil
369394
}
370395
msgString := "bad type provided for interface config"
371396
log.Error().Msgf(msgString)
@@ -376,11 +401,11 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
376401
if ok {
377402
log.Debug().Msg("config section exists for interface")
378403
// if `config` is provided, we'll overwrite the values in our
379-
// baseConfigTyped struct to act as the "new" base config.
404+
// pkgConfigCopy struct to act as the "new" base config.
380405
// This will allow us to set the default values for the interface
381406
// but override them further for each mock defined in the
382407
// `configs` section.
383-
decoder, err := c.getDecoder(baseConfigTyped)
408+
decoder, err := c.getDecoder(pkgConfigCopy)
384409
if err != nil {
385410
return nil, stackerr.NewStackErrf(err, "unable to create mapstructure decoder")
386411
}
@@ -397,8 +422,8 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
397422
configsSectionTyped := configsSection.([]any)
398423
for _, configMap := range configsSectionTyped {
399424
// Create a copy of the package-level config
400-
currentInterfaceConfig := reflect.New(reflect.ValueOf(baseConfigTyped).Elem().Type()).Interface()
401-
if err := copier.Copy(currentInterfaceConfig, baseConfigTyped); err != nil {
425+
currentInterfaceConfig := reflect.New(reflect.ValueOf(pkgConfigCopy).Elem().Type()).Interface()
426+
if err := copier.Copy(currentInterfaceConfig, pkgConfigCopy); err != nil {
402427
return nil, stackerr.NewStackErrf(err, "failed to copy package config")
403428
}
404429

@@ -418,7 +443,7 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
418443
log.Debug().Msg("configs section doesn't exist for interface")
419444

420445
if len(configs) == 0 {
421-
configs = append(configs, baseConfigTyped)
446+
configs = append(configs, pkgConfigCopy)
422447
}
423448
return configs, nil
424449
}
@@ -429,31 +454,33 @@ func (c *Config) addSubPkgConfig(ctx context.Context, subPkgPath string, parentP
429454
log := zerolog.Ctx(ctx).With().
430455
Str("parent-package", parentPkgPath).
431456
Str("sub-package", subPkgPath).Logger()
457+
ctx = log.WithContext(ctx)
432458

433-
log.Trace().Msg("adding sub-package to config map")
459+
log.Debug().Msg("adding sub-package to config map")
434460
parentPkgConfig, err := c.getPackageConfigMap(ctx, parentPkgPath)
435461
if err != nil {
436462
log.Err(err).
437463
Msg("failed to get package config for parent package")
438464
return fmt.Errorf("failed to get package config: %w", err)
439465
}
440466

441-
log.Trace().Msg("getting config")
442-
cfg, err := c.CfgAsMap(ctx)
467+
log.Debug().Msg("getting config")
468+
topLevelConfig, err := c.CfgAsMap(ctx)
443469
if err != nil {
444470
return fmt.Errorf("failed to get configuration map: %w", err)
445471
}
446472

447-
log.Trace().Msg("getting packages section")
448-
packagesSection := cfg["packages"].(map[string]any)
473+
log.Debug().Msg("getting packages section")
474+
packagesSection := topLevelConfig["packages"].(map[string]any)
449475

450-
// Don't overwrite any config that already exists
451476
_, pkgExists := packagesSection[subPkgPath]
452477
if !pkgExists {
453478
log.Trace().Msg("sub-package doesn't exist in config")
479+
480+
// Copy the parent package directly into the subpackage config section
454481
packagesSection[subPkgPath] = map[string]any{}
455482
newPkgSection := packagesSection[subPkgPath].(map[string]any)
456-
newPkgSection["config"] = parentPkgConfig["config"]
483+
newPkgSection["config"] = deepCopyConfigMap(parentPkgConfig["config"].(map[string]any))
457484
} else {
458485
log.Trace().Msg("sub-package exists in config")
459486
// The sub-package exists in config. Check if it has its
@@ -465,10 +492,15 @@ func (c *Config) addSubPkgConfig(ctx context.Context, subPkgPath string, parentP
465492
log.Err(err).Msg("could not get child package config")
466493
return fmt.Errorf("failed to get sub-package config: %w", err)
467494
}
468-
469-
for key, val := range parentPkgConfig {
470-
if _, keyInSubPkg := subPkgConfig[key]; !keyInSubPkg {
471-
subPkgConfig[key] = val
495+
log.Trace().Msgf("sub-package config: %v", subPkgConfig)
496+
log.Trace().Msgf("parent-package config: %v", parentPkgConfig)
497+
498+
// Merge the parent config with the sub-package config.
499+
parentConfigSection := parentPkgConfig["config"].(map[string]any)
500+
subPkgConfigSection := subPkgConfig["config"].(map[string]any)
501+
for key, val := range parentConfigSection {
502+
if _, keyInSubPkg := subPkgConfigSection[key]; !keyInSubPkg {
503+
subPkgConfigSection[key] = val
472504
}
473505

474506
}
@@ -595,18 +627,24 @@ func (c *Config) subPackages(
595627
// recursive and recurses the file tree to find all sub-packages.
596628
func (c *Config) discoverRecursivePackages(ctx context.Context) error {
597629
log := zerolog.Ctx(ctx)
630+
log.Trace().Msg("discovering recursive packages")
598631
recursivePackages := map[string]*Config{}
599632
packageList, err := c.GetPackages(ctx)
600633
if err != nil {
601634
return fmt.Errorf("failed to get packages: %w", err)
602635
}
603636
for _, pkg := range packageList {
604637
pkgConfig, err := c.GetPackageConfig(ctx, pkg)
638+
pkgLog := log.With().Str("package", pkg).Logger()
639+
pkgLog.Trace().Msg("iterating over package")
605640
if err != nil {
606641
return fmt.Errorf("failed to get package config: %w", err)
607642
}
608643
if pkgConfig.Recursive {
644+
pkgLog.Trace().Msg("package marked as recursive")
609645
recursivePackages[pkg] = pkgConfig
646+
} else {
647+
pkgLog.Trace().Msg("package not marked as recursive")
610648
}
611649
}
612650
if len(recursivePackages) == 0 {
@@ -658,6 +696,17 @@ func contains[T comparable](slice []T, elem T) bool {
658696
return false
659697
}
660698

699+
func deepCopyConfigMap(src map[string]any) map[string]any {
700+
newMap := map[string]any{}
701+
for key, val := range src {
702+
if contains([]string{"packages", "config", "interfaces"}, key) {
703+
continue
704+
}
705+
newMap[key] = val
706+
}
707+
return newMap
708+
}
709+
661710
// mergeInConfig takes care of merging inheritable configuration
662711
// in the config map. For example, it merges default config, then
663712
// package-level config, then interface-level config.
@@ -677,34 +726,62 @@ func (c *Config) mergeInConfig(ctx context.Context) error {
677726
}
678727
for _, pkgPath := range pkgs {
679728
pkgLog := log.With().Str("package-path", pkgPath).Logger()
729+
pkgCtx := pkgLog.WithContext(ctx)
730+
680731
pkgLog.Trace().Msg("merging for package")
681-
packageConfig, err := c.getPackageConfigMap(ctx, pkgPath)
732+
packageConfig, err := c.getPackageConfigMap(pkgCtx, pkgPath)
682733
if err != nil {
683734
pkgLog.Err(err).Msg("failed to get package config")
684735
return fmt.Errorf("failed to get package config: %w", err)
685736
}
737+
pkgLog.Trace().Msgf("got package config map: %v", packageConfig)
738+
686739
configSectionUntyped, configExists := packageConfig["config"]
687740
if !configExists {
688-
packageConfig["config"] = defaultCfg
689-
continue
741+
// The reason why this should never happen is because getPackageConfigMap
742+
// should be populating the config section with the top-level config if it
743+
// wasn't defined in the yaml.
744+
msg := "config section does not exist for package, this should never happen"
745+
pkgLog.Error().Msg(msg)
746+
return fmt.Errorf(msg)
747+
}
748+
749+
pkgLog.Trace().Msg("got config section for package")
750+
// Sometimes the config section may be provided, but it's nil.
751+
// We need to account for this fact.
752+
if configSectionUntyped == nil {
753+
pkgLog.Trace().Msg("config section is nil, converting to empty map")
754+
emptyMap := map[string]any{}
755+
756+
// We need to add this to the "global" config mapping so the change
757+
// gets persisted, and also into configSectionUntyped for the logic
758+
// further down.
759+
packageConfig["config"] = emptyMap
760+
configSectionUntyped = emptyMap
761+
} else {
762+
pkgLog.Trace().Msg("config section is not nil")
690763
}
691-
packageConfigSection := configSectionUntyped.(map[string]any)
764+
765+
configSectionTyped := configSectionUntyped.(map[string]any)
692766

693767
for key, value := range defaultCfg {
694768
if contains([]string{"packages", "config"}, key) {
695769
continue
696770
}
697-
_, keyExists := packageConfigSection[key]
771+
keyValLog := pkgLog.With().Str("key", key).Str("value", fmt.Sprintf("%v", value)).Logger()
772+
773+
_, keyExists := configSectionTyped[key]
698774
if !keyExists {
699-
packageConfigSection[key] = value
775+
keyValLog.Trace().Msg("setting key to value")
776+
configSectionTyped[key] = value
700777
}
701778
}
702-
interfaces, err := c.getInterfacesForPackage(ctx, pkgPath)
779+
interfaces, err := c.getInterfacesForPackage(pkgCtx, pkgPath)
703780
if err != nil {
704781
return fmt.Errorf("failed to get interfaces for package: %w", err)
705782
}
706783
for _, interfaceName := range interfaces {
707-
interfacesSection, err := c.getInterfacesSection(ctx, pkgPath)
784+
interfacesSection, err := c.getInterfacesSection(pkgCtx, pkgPath)
708785
if err != nil {
709786
return err
710787
}
@@ -728,7 +805,7 @@ func (c *Config) mergeInConfig(ctx context.Context) error {
728805
// Assume this interface's value in the map is nil. Just skip it.
729806
continue
730807
}
731-
for key, value := range packageConfigSection {
808+
for key, value := range configSectionTyped {
732809
if key == "packages" {
733810
continue
734811
}

0 commit comments

Comments
 (0)