mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
Provisioning: Remove again dependency cycle between provisioning app and grafana (#110863)
* Remove dependency cycle between provisioning app and grafana * Format code * Fix linting
This commit is contained in:
parent
5520a38726
commit
09ef9c8176
7 changed files with 445 additions and 16 deletions
|
|
@ -6,7 +6,6 @@ require (
|
|||
github.com/google/go-github/v70 v70.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43
|
||||
github.com/grafana/grafana v6.1.6+incompatible
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3
|
||||
github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
|
||||
|
|
@ -57,10 +56,8 @@ require (
|
|||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
|
|||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ=
|
||||
|
|
@ -58,8 +56,6 @@ github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/
|
|||
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE=
|
||||
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E=
|
||||
github.com/grafana/grafana v6.1.6+incompatible h1:Eyeg3ifz220cWiu0hLoPjJJmle+nxR63ZEmSDgYDokg=
|
||||
github.com/grafana/grafana v6.1.6+incompatible/go.mod h1:U8QyUclJHj254BFcuw45p6sg7eeGYX44qn1ShYo5rGE=
|
||||
github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU=
|
||||
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3 h1:2VXsXXEQiqAavRP8wusRDB6rDqf5lufP7A6NfjELqPE=
|
||||
|
|
@ -74,8 +70,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
|||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
|
|
@ -118,10 +112,6 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
|
|||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -131,8 +121,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
|
||||
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository/git"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/util"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
|
|||
58
apps/provisioning/pkg/util/interface.go
Normal file
58
apps/provisioning/pkg/util/interface.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package util
|
||||
|
||||
import "reflect"
|
||||
|
||||
// IsInterfaceNil checks if an interface is nil or holds a nil value.
|
||||
//
|
||||
// This function addresses the Go "nil interface" gotcha where an interface
|
||||
// can be != nil but still hold a nil value of a specific type.
|
||||
//
|
||||
// The Problem:
|
||||
// In Go, an interface value consists of two parts: a type and a value.
|
||||
// An interface is only considered nil when both parts are nil.
|
||||
// However, if you assign a typed nil (e.g., (*MyType)(nil)) to an interface,
|
||||
// the interface becomes != nil even though it holds a nil value.
|
||||
//
|
||||
// Example of the gotcha:
|
||||
//
|
||||
// var p *int = nil // p is a nil pointer
|
||||
// var i interface{} = p // i holds a typed nil (*int)(nil)
|
||||
// fmt.Println(i == nil) // prints: false (this is the gotcha!)
|
||||
// fmt.Println(IsInterfaceNil(i)) // prints: true (correctly identifies nil)
|
||||
//
|
||||
// Common scenario with error interfaces:
|
||||
//
|
||||
// func doSomething() error {
|
||||
// var err *MyError = nil
|
||||
// if someCondition {
|
||||
// err = &MyError{msg: "failed"}
|
||||
// }
|
||||
// return err // returns interface{} containing (*MyError)(nil)
|
||||
// }
|
||||
//
|
||||
// if err := doSomething(); err != nil { // this check fails!
|
||||
// // This code won't run even when err contains nil
|
||||
// }
|
||||
//
|
||||
// if err := doSomething(); !IsInterfaceNil(err) {
|
||||
// // This correctly identifies the nil error
|
||||
// }
|
||||
//
|
||||
// Supported types: Ptr, Slice, Map, Func, Interface
|
||||
// Unsupported nilable types: Chan, UnsafePointer (these return false even when nil)
|
||||
//
|
||||
// See more about this Go gotcha at:
|
||||
// https://go.dev/doc/faq#nil_error
|
||||
// https://medium.com/@moksh.9/go-gotcha-when-nil-isnt-really-nil-ddf632720001
|
||||
func IsInterfaceNil(i interface{}) bool {
|
||||
iv := reflect.ValueOf(i)
|
||||
if !iv.IsValid() {
|
||||
return true
|
||||
}
|
||||
switch iv.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Func, reflect.Interface:
|
||||
return iv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
172
apps/provisioning/pkg/util/interface_test.go
Normal file
172
apps/provisioning/pkg/util/interface_test.go
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsInterfaceNil(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
expected bool
|
||||
}{
|
||||
// True nil cases
|
||||
{"true nil interface", nil, true},
|
||||
{"nil pointer", (*int)(nil), true},
|
||||
{"nil slice", ([]int)(nil), true},
|
||||
{"nil map", (map[string]int)(nil), true},
|
||||
{"nil function", (func())(nil), true},
|
||||
{"nil interface wrapped in interface", (interface{})(nil), true},
|
||||
|
||||
// Channels are not handled by IsInterfaceNil (not in switch statement)
|
||||
{"nil channel - not handled", (chan int)(nil), false},
|
||||
|
||||
// Non-nil cases
|
||||
{"non-nil pointer", func() interface{} { val := 42; return &val }(), false},
|
||||
{"non-nil slice", []int{1, 2, 3}, false},
|
||||
{"empty slice", []int{}, false},
|
||||
{"non-nil map", map[string]int{"key": 1}, false},
|
||||
{"empty map", make(map[string]int), false},
|
||||
{"non-nil function", func() {}, false},
|
||||
{"non-nil channel", make(chan int), false},
|
||||
|
||||
// Basic value types
|
||||
{"string value", "hello", false},
|
||||
{"empty string", "", false},
|
||||
{"int value", 42, false},
|
||||
{"zero int", 0, false},
|
||||
{"bool true", true, false},
|
||||
{"bool false", false, false},
|
||||
{"float64", 3.14, false},
|
||||
{"complex128", complex(1, 2), false},
|
||||
|
||||
// Composite value types
|
||||
{"struct value", struct{ x int }{x: 1}, false},
|
||||
{"array value", [3]int{1, 2, 3}, false},
|
||||
{"zero array", [3]int{}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsInterfaceNil(tc.value)
|
||||
assert.Equal(t, tc.expected, result, "IsInterfaceNil(%v) should return %t", tc.value, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInterfaceNil_NestedInterfaces(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nested interface with nil",
|
||||
value: func() interface{} {
|
||||
var inner *int = nil
|
||||
var middle interface{} = inner
|
||||
return middle
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "nested interface with value",
|
||||
value: func() interface{} {
|
||||
val := 42
|
||||
inner := &val
|
||||
var middle interface{} = inner
|
||||
return middle
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "interface containing interface with value",
|
||||
value: func() interface{} {
|
||||
var inner interface{} = 42
|
||||
outer := inner
|
||||
return outer
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "interface containing nil interface",
|
||||
value: func() interface{} {
|
||||
var inner interface{} = nil
|
||||
outer := inner
|
||||
return outer
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsInterfaceNil(tc.value)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInterfaceNil_ReflectKinds(t *testing.T) {
|
||||
t.Run("handles specific nilable reflect kinds", func(t *testing.T) {
|
||||
// Test the specific nilable kinds that the function handles
|
||||
// according to its switch statement: Ptr, Slice, Map, Func, Interface
|
||||
nilableTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"nil pointer", (*int)(nil)},
|
||||
{"nil slice", ([]int)(nil)},
|
||||
{"nil map", (map[string]int)(nil)},
|
||||
{"nil function", (func())(nil)},
|
||||
{"nil interface", (interface{})(nil)},
|
||||
}
|
||||
|
||||
for _, tc := range nilableTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.True(t, IsInterfaceNil(tc.value), "%s should be detected as nil", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not handle channels and other nilable types", func(t *testing.T) {
|
||||
// Test that nilable kinds NOT in the switch statement return false
|
||||
unhandledTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"nil channel", (chan int)(nil)},
|
||||
// UnsafePointer would be another example, but harder to test
|
||||
}
|
||||
|
||||
for _, tc := range unhandledTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (unhandled type)", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles non-nilable kinds", func(t *testing.T) {
|
||||
// Test kinds that cannot be nil
|
||||
nonNilableTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"int", 42},
|
||||
{"string", "test"},
|
||||
{"bool", true},
|
||||
{"struct", struct{ x int }{x: 1}},
|
||||
{"array", [3]int{1, 2, 3}},
|
||||
{"float64", 3.14},
|
||||
{"complex128", complex(1, 2)},
|
||||
}
|
||||
|
||||
for _, tc := range nonNilableTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (non-nilable)", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -2,6 +2,48 @@ package util
|
|||
|
||||
import "reflect"
|
||||
|
||||
// IsInterfaceNil checks if an interface is nil or holds a nil value.
|
||||
//
|
||||
// This function addresses the Go "nil interface" gotcha where an interface
|
||||
// can be != nil but still hold a nil value of a specific type.
|
||||
//
|
||||
// The Problem:
|
||||
// In Go, an interface value consists of two parts: a type and a value.
|
||||
// An interface is only considered nil when both parts are nil.
|
||||
// However, if you assign a typed nil (e.g., (*MyType)(nil)) to an interface,
|
||||
// the interface becomes != nil even though it holds a nil value.
|
||||
//
|
||||
// Example of the gotcha:
|
||||
//
|
||||
// var p *int = nil // p is a nil pointer
|
||||
// var i interface{} = p // i holds a typed nil (*int)(nil)
|
||||
// fmt.Println(i == nil) // prints: false (this is the gotcha!)
|
||||
// fmt.Println(IsInterfaceNil(i)) // prints: true (correctly identifies nil)
|
||||
//
|
||||
// Common scenario with error interfaces:
|
||||
//
|
||||
// func doSomething() error {
|
||||
// var err *MyError = nil
|
||||
// if someCondition {
|
||||
// err = &MyError{msg: "failed"}
|
||||
// }
|
||||
// return err // returns interface{} containing (*MyError)(nil)
|
||||
// }
|
||||
//
|
||||
// if err := doSomething(); err != nil { // this check fails!
|
||||
// // This code won't run even when err contains nil
|
||||
// }
|
||||
//
|
||||
// if err := doSomething(); !IsInterfaceNil(err) {
|
||||
// // This correctly identifies the nil error
|
||||
// }
|
||||
//
|
||||
// Supported types: Ptr, Slice, Map, Func, Interface
|
||||
// Unsupported nilable types: Chan, UnsafePointer (these return false even when nil)
|
||||
//
|
||||
// See more about this Go gotcha at:
|
||||
// https://go.dev/doc/faq#nil_error
|
||||
// https://medium.com/@moksh.9/go-gotcha-when-nil-isnt-really-nil-ddf632720001
|
||||
func IsInterfaceNil(i interface{}) bool {
|
||||
iv := reflect.ValueOf(i)
|
||||
if !iv.IsValid() {
|
||||
|
|
|
|||
172
pkg/util/interface_test.go
Normal file
172
pkg/util/interface_test.go
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsInterfaceNil(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
expected bool
|
||||
}{
|
||||
// True nil cases
|
||||
{"true nil interface", nil, true},
|
||||
{"nil pointer", (*int)(nil), true},
|
||||
{"nil slice", ([]int)(nil), true},
|
||||
{"nil map", (map[string]int)(nil), true},
|
||||
{"nil function", (func())(nil), true},
|
||||
{"nil interface wrapped in interface", (interface{})(nil), true},
|
||||
|
||||
// Channels are not handled by IsInterfaceNil (not in switch statement)
|
||||
{"nil channel - not handled", (chan int)(nil), false},
|
||||
|
||||
// Non-nil cases
|
||||
{"non-nil pointer", func() interface{} { val := 42; return &val }(), false},
|
||||
{"non-nil slice", []int{1, 2, 3}, false},
|
||||
{"empty slice", []int{}, false},
|
||||
{"non-nil map", map[string]int{"key": 1}, false},
|
||||
{"empty map", make(map[string]int), false},
|
||||
{"non-nil function", func() {}, false},
|
||||
{"non-nil channel", make(chan int), false},
|
||||
|
||||
// Basic value types
|
||||
{"string value", "hello", false},
|
||||
{"empty string", "", false},
|
||||
{"int value", 42, false},
|
||||
{"zero int", 0, false},
|
||||
{"bool true", true, false},
|
||||
{"bool false", false, false},
|
||||
{"float64", 3.14, false},
|
||||
{"complex128", complex(1, 2), false},
|
||||
|
||||
// Composite value types
|
||||
{"struct value", struct{ x int }{x: 1}, false},
|
||||
{"array value", [3]int{1, 2, 3}, false},
|
||||
{"zero array", [3]int{}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsInterfaceNil(tc.value)
|
||||
assert.Equal(t, tc.expected, result, "IsInterfaceNil(%v) should return %t", tc.value, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInterfaceNil_NestedInterfaces(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nested interface with nil",
|
||||
value: func() interface{} {
|
||||
var inner *int = nil
|
||||
var middle interface{} = inner
|
||||
return middle
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "nested interface with value",
|
||||
value: func() interface{} {
|
||||
val := 42
|
||||
inner := &val
|
||||
var middle interface{} = inner
|
||||
return middle
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "interface containing interface with value",
|
||||
value: func() interface{} {
|
||||
var inner interface{} = 42
|
||||
outer := inner
|
||||
return outer
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "interface containing nil interface",
|
||||
value: func() interface{} {
|
||||
var inner interface{} = nil
|
||||
outer := inner
|
||||
return outer
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsInterfaceNil(tc.value)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInterfaceNil_ReflectKinds(t *testing.T) {
|
||||
t.Run("handles specific nilable reflect kinds", func(t *testing.T) {
|
||||
// Test the specific nilable kinds that the function handles
|
||||
// according to its switch statement: Ptr, Slice, Map, Func, Interface
|
||||
nilableTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"nil pointer", (*int)(nil)},
|
||||
{"nil slice", ([]int)(nil)},
|
||||
{"nil map", (map[string]int)(nil)},
|
||||
{"nil function", (func())(nil)},
|
||||
{"nil interface", (interface{})(nil)},
|
||||
}
|
||||
|
||||
for _, tc := range nilableTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.True(t, IsInterfaceNil(tc.value), "%s should be detected as nil", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not handle channels and other nilable types", func(t *testing.T) {
|
||||
// Test that nilable kinds NOT in the switch statement return false
|
||||
unhandledTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"nil channel", (chan int)(nil)},
|
||||
// UnsafePointer would be another example, but harder to test
|
||||
}
|
||||
|
||||
for _, tc := range unhandledTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (unhandled type)", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles non-nilable kinds", func(t *testing.T) {
|
||||
// Test kinds that cannot be nil
|
||||
nonNilableTestCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{"int", 42},
|
||||
{"string", "test"},
|
||||
{"bool", true},
|
||||
{"struct", struct{ x int }{x: 1}},
|
||||
{"array", [3]int{1, 2, 3}},
|
||||
{"float64", 3.14},
|
||||
{"complex128", complex(1, 2)},
|
||||
}
|
||||
|
||||
for _, tc := range nonNilableTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (non-nilable)", tc.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue