Skip to content

Commit

Permalink
Bug fixes for regression test generation and OverridesError (#45)
Browse files Browse the repository at this point in the history
- DetailedError() did not have a test [fixed]
- OverridesError() only partially worked [fixed]
- OverridesError() test was incorrect [fixed]
- Automatic creation of regression tests missed many decorators [fixed]
- README now has instructions for getting generated regression tests
  • Loading branch information
muir authored Aug 6, 2022
1 parent 3a5f824 commit 18541fc
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 12 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ Injection chain errors attempt to be self-explanatory, but sometimes that's not
If you're building your injection sequence dynamically, it may be useful to print
the injection chain. It has a `String()` method.

If you think that the issue is with nject, then open an issue with the reproduce information
that is available as part of the debugging (`Debugging.Reproduce`) or avilable by calling
`nject.DetailedError(err)` on the error returned from `Bind()` or `Run()`.

# Related packages

The following use nject to provide nicer APIs:
Expand Down
47 changes: 39 additions & 8 deletions debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func generateReproduce(funcs []*provider, invokeF *provider, initF *provider) st
f += "\t\trequire.NoError(t,\n"
f += "\t\t\tSequence(\"regression\",\n"

var inCluster int32
for _, fm := range funcs {
if fm == nil {
continue
Expand All @@ -218,18 +219,42 @@ func generateReproduce(funcs []*provider, invokeF *provider, initF *provider) st
if fm.fn == nil {
continue
}
// f += fmt.Sprintf("\t\t\t\t// %s\n", fm)
f += "\t\t\t\t"
if fm.cluster != 0 {
switch inCluster {
case 0:
f += fmt.Sprintf("\t\t\t\tCluster(\"c%d\",\n", fm.cluster-1)
case fm.cluster:
// do nothing
default:
f += fmt.Sprintf("\t\t\t\t),\n\t\t\t\tCluster(\"c%d\",\n", fm.cluster-1)
}
inCluster = fm.cluster
} else if inCluster != 0 {
f += "\t\t\t\t),\n"
inCluster = 0
}
var extraIndent string
if inCluster != 0 {
extraIndent = "\t"
}
f += "\t\t\t\t" + extraIndent
close := ""
for annotation, active := range map[string]bool{
"NonFinal": fm.nonFinal,
"Cacheable": fm.cacheable,
"MustCache": fm.mustCache,
"Required": fm.required,
"Desired": fm.desired,
"CallsInner": fm.callsInner,
"Memoize": fm.memoize,
"Loose": fm.loose,
"Reorder": fm.reorder,
"OverridesError": fm.overridesError,
"Desired": fm.desired,
"Shun": fm.shun,
"NotCacheable": fm.notCacheable,
"MustConsume": fm.mustConsume,
"ConsumptionOptional": fm.consumptionOptional,
"Singleton": fm.singleton,
} {
if active {
f += annotation + "("
Expand All @@ -247,7 +272,10 @@ func generateReproduce(funcs []*provider, invokeF *provider, initF *provider) st
f += "func("
skip := 0
if fm.class == wrapperFunc {
f += "inner " + funcSig(subs, &t, typ.In(0)) + ", "
f += "inner " + funcSig(subs, &t, typ.In(0))
if len(typesIn(typ)) > 1 {
f += ", "
}
skip = 1
}
f += strings.Join(addVarnames(substituteTypes(subs, &t, typesIn(typ)[skip:])), ", ") + ") "
Expand All @@ -262,10 +290,10 @@ func generateReproduce(funcs []*provider, invokeF *provider, initF *provider) st
}
if fm.class == wrapperFunc {
f += " {\n"
f += fmt.Sprintf("\t\t\t\t\tcalled[%q]++\n", n)
f += "\t\t\t\t\tinner(" + strings.Join(substituteDefaults(subs, typesIn(typ.In(0))), ", ") + ")\n"
f += "\t\t\t\t\treturn " + strings.Join(substituteDefaults(subs, out), ", ") + "\n"
f += "\t\t\t\t}"
f += fmt.Sprintf("%s\t\t\t\t\tcalled[%q]++\n", extraIndent, n)
f += extraIndent + "\t\t\t\t\tinner(" + strings.Join(substituteDefaults(subs, typesIn(typ.In(0))), ", ") + ")\n"
f += extraIndent + "\t\t\t\t\treturn " + strings.Join(substituteDefaults(subs, out), ", ") + "\n"
f += extraIndent + "\t\t\t\t}"
} else {
f += fmt.Sprintf(" { called[%q]++; return %s }", n, strings.Join(substituteDefaults(subs, out), ", "))
}
Expand All @@ -280,6 +308,9 @@ func generateReproduce(funcs []*provider, invokeF *provider, initF *provider) st
f += fmt.Sprintf("%s(%s)%s,\n", tca[0], def[0], close)
}
}
if inCluster != 0 {
f += "\t\t\t),\n"
}
f += "\t\t\t).Bind(&invoker, " + initName + "))\n"
if initF != nil {
f += "\t\tiniter(" + strings.Join(substituteDefaults(subs, typesIn(elem(initF.fn))), ", ") + ")\n"
Expand Down
60 changes: 60 additions & 0 deletions debug_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package nject

import (
"fmt"
"regexp"
"strings"
"sync/atomic"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func debugOn(t *testing.T) {
Expand Down Expand Up @@ -34,3 +40,57 @@ func wrapTest(t *testing.T, inner func(*testing.T)) {
})
}
}

func TestDetailedError(t *testing.T) {
t.Parallel()

type MyType1 struct {
Int int
}
type MyType2 []MyType1
type MyType3 *MyType1
type MyType4 interface {
String() string
}
type MyType5 interface {
unimplementable()
}

err := Run("expected-to-fail",
Desired(func() MyType1 { return MyType1{} }),
Shun(func(m MyType1) MyType3 { return &m }),
Required(func(m MyType3) MyType2 { return []MyType1{*m} }),
Cacheable(func() int { return 4 }),
MustCache(func() string { return "foo" }),
Cluster("c1",
Singleton(func(i int) int64 { return int64(i) }),
Loose(func(m MyType4) string { return m.String() }),
),
Cluster("c2",
Reorder(func() time.Time { return time.Now() }),
NotCacheable(func(i int) int32 { return int32(i) }),
),
// CallsInner(func(i func()) { i() }),
Memoize(func(i int32) int32 { return i }),
OverridesError(func(i func()) error { return nil }),
MustConsume(func(i int32) int64 { return int64(i) }),
ConsumptionOptional(func(i int64) float64 { return float64(i) }),
func(m MyType5) error { return nil },
NonFinal(func() {}),
)
require.Error(t, err, "mess from the above")
detailed := DetailedError(err)
require.NotEqual(t, err.Error(), detailed, "detailed should have more")
t.Log("detailed error", detailed)

index := strings.Index(detailed, "func TestRegression")
require.NotEqual(t, -1, index, "contains 'func TestRegression'")
detailed = detailed[index:]

for _, word := range strings.Split("Desired Shun Required Cacheable MustCache Cluster Memoize OverridesError MustConsume ConsumptionOptional NonFinal", " ") {
re := regexp.MustCompile(fmt.Sprintf(`\b%s\(` /*)*/, word))
if !re.MatchString(detailed) {
t.Errorf("did not find %s( in reproduce output", word) // )
}
}
}
3 changes: 2 additions & 1 deletion nject.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type provider struct {
fn interface{}
id int32

// user annotations
// user annotations (match these in debug.go)
nonFinal bool
cacheable bool
mustCache bool
Expand Down Expand Up @@ -86,6 +86,7 @@ func (fm *provider) copy() *provider {
memoize: fm.memoize,
loose: fm.loose,
reorder: fm.reorder,
overridesError: fm.overridesError,
desired: fm.desired,
shun: fm.shun,
notCacheable: fm.notCacheable,
Expand Down
6 changes: 3 additions & 3 deletions overrides_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ func TestOverridesError(t *testing.T) {
t.Log("test: should fail because there is a terminal-error injector that gets clobbered")
assert.Error(t, Sequence("C", danger, returnsTerminal, finalWithoutError).Bind(&target, nil))

t.Log("test: okay because marked even thoguh the final function returns error that gets clobbered")
assert.Error(t, Sequence("B", OverridesError(danger), finalWithError).Bind(&target, nil))
t.Log("test: okay because marked even though the final function returns error that gets clobbered")
assert.NoError(t, Sequence("B", OverridesError(danger), finalWithError).Bind(&target, nil))

t.Log("test: okay because marked even though there is a terminal-error injector that gets clobbered")
assert.Error(t, Sequence("C", OverridesError(danger), returnsTerminal, finalWithoutError).Bind(&target, nil))
assert.NoError(t, Sequence("C", OverridesError(danger), returnsTerminal, finalWithoutError).Bind(&target, nil))
}

0 comments on commit 18541fc

Please sign in to comment.