Skip to content

Commit

Permalink
Detect deps that have already been invoked (#346)
Browse files Browse the repository at this point in the history
* Detect deps that have already been invoked

Detect when a dependency was accidentally already invoked

mg.Deps(MyDep())

Normally this causes a nil pointer panic when we invoke the function

Error: runtime error: invalid memory address or nil pointer dereference

Now it will print the following as soon as its detected

Error: A dependency of the current target was defined improperly, with parenthesis. Dependencies should be defined as mg.Deps(MyDep), not mg.Deps(MyDep())

I have applied this change to checkfn so that it applies to serial and
parallel deps.

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>

* Fix nil checks and improve error message

The new mg.F function is the root of the nil-pointer error that can happen
when someone accidentally calls mg.Deps(TargetA()). I have fixed the check
for whether a function was passed so that it won't panic and included
the usage for mg.F in the error message.

I then added a similar check, is the target a function, to
Deps/SerialDeps/CxtSerialDeps functions so that we can give a better
error message when it's passed a non-function. The error message
otherwise will reference mg.F which I don't think will help point people
to the real problem, which is how they called mg.Deps/SerialDeps.

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>

Co-authored-by: Nate Finch <nate.finch@gmail.com>
  • Loading branch information
carolynvs and natefinch authored Jan 16, 2022
1 parent 3a11bce commit c07f313
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 2 deletions.
7 changes: 7 additions & 0 deletions mg/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ func checkFns(fns []interface{}) []Fn {
funcs[i] = fn
continue
}

// Check if the target provided is a not function so we can give a clear warning
t := reflect.TypeOf(f)
if t == nil || t.Kind() != reflect.Func {
panic(fmt.Errorf("non-function used as a target dependency: %T. The mg.Deps, mg.SerialDeps and mg.CtxDeps functions accept function names, such as mg.Deps(TargetA, TargetB)", f))
}

funcs[i] = F(f)
}
return funcs
Expand Down
21 changes: 21 additions & 0 deletions mg/deps_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mg

import (
"bytes"
"fmt"
"log"
"os"
"strings"
Expand Down Expand Up @@ -33,3 +34,23 @@ func bar() {
}

func baz() {}

func TestDepWasNotInvoked(t *testing.T) {
fn1 := func() error {
return nil
}
defer func() {
err := recover()
if err == nil {
t.Fatal("expected panic, but didn't get one")
}
gotErr := fmt.Sprint(err)
wantErr := "non-function used as a target dependency: <nil>. The mg.Deps, mg.SerialDeps and mg.CtxDeps functions accept function names, such as mg.Deps(TargetA, TargetB)"
if !strings.Contains(gotErr, wantErr) {
t.Fatalf(`expected to get "%s" but got "%s"`, wantErr, gotErr)
}
}()
func(fns ...interface{}) {
checkFns(fns)
}(fn1())
}
4 changes: 2 additions & 2 deletions mg/fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ func (f fn) Run(ctx context.Context) error {

func checkF(target interface{}, args []interface{}) (hasContext, isNamespace bool, _ error) {
t := reflect.TypeOf(target)
if t.Kind() != reflect.Func {
return false, false, fmt.Errorf("non-function passed to mg.F: %T", target)
if t == nil || t.Kind() != reflect.Func {
return false, false, fmt.Errorf("non-function passed to mg.F: %T. The mg.F function accepts function names, such as mg.F(TargetA, \"arg1\", \"arg2\")", target)
}

if t.NumOut() > 1 {
Expand Down
10 changes: 10 additions & 0 deletions mg/fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ func TestFuncCheck(t *testing.T) {
if err == nil {
t.Error("expected func(*int) error to be invalid")
}

defer func() {
if r := recover(); r !=nil {
t.Error("expected a nil function argument to be handled gracefully")
}
}()
_, _, err = checkF(nil, []interface{}{1,2})
if err == nil {
t.Error("expected a nil function argument to be invalid")
}
}

func TestF(t *testing.T) {
Expand Down

0 comments on commit c07f313

Please sign in to comment.