Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testscript: test custom conditions #175

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
27 changes: 27 additions & 0 deletions testscript/doc.go
Expand Up @@ -121,6 +121,33 @@ A condition can be negated: [!short] means to run the rest of the line
when testing.Short() is false.

Additional conditions can be added by passing a function to Params.Condition.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth mentioning this:

The function will only be called when all built-in conditions have been checked for.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

The function will only be called when all built-in conditions have been checked for.

An example:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this to be a separate paragraph, IMO. You can attach it to the previous paragraph.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Condition: func(cond string) (bool, error) {
// Assume condition name and args are separated by colon (":")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that parsing the condition here is somewhat overkill tbh, and makes the example a bit longer than it needs to be. I'm pretty sure that people writing conditions are up to the task of extrapolating from something a bit simpler.

How about just something like:

Condition: func(cond string) (bool, error) {
    // Note: conditions can be more complex than this (for example [exists:/some/file]).
    if cond != "someCondition" {
        return false, fmt.Errorf("unknown condition")
    }
    // someCondition is a place-holder for any conditional logic you might want.
    return someCondition(), nil
}

?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that, if everyone agrees. However, I wanted the example to include some text manipulation logic, because I was baffled when I first tried to define a condition. What was unclear to me was that custom commands are defined as a map of functions, while custom conditions are just one function for all.
I think the current example will save users some grief.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, I also feel this is likely to confuse people. But perhaps what this is really telling us is that the API should be a map of condition names to func(string) (bool, error). What do you think?

args := strings.Split(cond, ":")
name := args[0]
switch name {
case "exists":
if len(args) < 2 {
return false, fmt.Errorf("syntax: [exists:file_name]")
}
_, err := os.Stat(args[1])
return !errors.Is(err, fs.ErrNotExist), nil
case "long":
return os.Getenv("TEST_LONG") != "", nil
default:
return false, fmt.Errorf("unrecognized condition %s", name)
}
},

With the conditions so defined, you can use them as follows:
env file_name=/path/to/filename
[exists:$file_name] exec echo 'file was found'
env loops=1
[long] env loops=3
exec do_something $loops

The predefined commands are:

Expand Down
67 changes: 67 additions & 0 deletions testscript/testdata/custom_conditions.txt
@@ -0,0 +1,67 @@
[!exec:echo] skip
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a test that for the custom condition function returning an error, but that might be a bit awkward to do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find a way to do that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPDATE: I found a way to test the errors (see testdata/custom_conditions_errors.txt).
Admittedly, it's a hack. It may not be resilient enough to certify future code update.


env upper_word=ABCD
env lower_word=abcd

[always_true] exec echo 'this is true'
stdout 'this is true'

exec echo ''

[!always_true] exec echo 'this is true'
! stdout .

exec echo ''

[always_false] exec echo 'this is false'
! stdout .

exec echo ''

[!always_false] exec echo 'this is false'
stdout 'this is false'

exec echo ''

[is_upper:ABCD] exec echo 'it is upper'
stdout 'it is upper'

exec echo ''

[is_upper:$upper_word] exec echo 'it is again upper'
stdout 'it is again upper'

exec echo ''

[is_upper:abcd] exec echo 'it is upper'
! stdout .

exec echo ''

[is_upper:$lower_word] exec echo 'it is lower'
! stdout .

exec echo ''

[!is_upper:ABCD] exec echo 'it is upper'
! stdout .

exec echo ''

[is_lower:abcd] exec echo 'it is lower'
stdout 'it is lower'

exec echo ''

[is_lower:$lower_word] exec echo 'it is again lower'
stdout 'it is again lower'

exec echo ''

[is_lower:ABCD] exec echo 'it is lower'
! stdout .

exec echo ''

[!is_lower:$lower_word] exec echo 'it is lower'
! stdout .
72 changes: 72 additions & 0 deletions testscript/testdata/custom_conditions_errors.txt
@@ -0,0 +1,72 @@
env GOPATH=$WORK
env GOCACHE=$WORK/.cache

[!exec:echo] skip
cd cond_errors
exec go mod tidy
! exec go test -run TestConditionErrors/is_upper-no-parameter
stdout 'FAIL'
stdout 'syntax: \[is_upper:word\]'

! exec go test -run TestConditionErrors/is_lower-no-parameter
stdout 'FAIL'
stdout 'syntax: \[is_lower:word\]'

! exec go test -run TestConditionErrors/unrecognized
stdout 'FAIL'
stdout 'unrecognized condition something'

-- cond_errors/main_test.go --
package condition_errors

import (
"fmt"
"strings"
"testing"

"github.com/rogpeppe/go-internal/testscript"
)

func TestConditionErrors(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "testdata",
Condition: func(cond string) (bool, error) {
// Assume condition name and args are separated by colon (":")
args := strings.Split(cond, ":")
name := args[0]
switch name {
case "is_upper":
if len(args) < 2 {
return false, fmt.Errorf("syntax: [is_upper:word]")
}
return strings.ToUpper(args[1]) == args[1], nil
case "is_lower":
if len(args) < 2 {
return false, fmt.Errorf("syntax: [is_lower:word]")
}
return strings.ToLower(args[1]) == args[1], nil
case "always_true":
return true, nil
case "always_false":
return false, nil
default:
return false, fmt.Errorf("unrecognized condition %s", name)
}
},
})
}
-- cond_errors/go.mod --
module condition_errors

go 1.18

require (
github.com/rogpeppe/go-internal v1.9.0
)

-- cond_errors/testdata/is_upper-no-parameter.txt --
[is_upper] exec echo ''
-- cond_errors/testdata/is_lower-no-parameter.txt --
[is_lower] exec echo ''
-- cond_errors/testdata/unrecognized.txt --
[something] exec echo ''
23 changes: 23 additions & 0 deletions testscript/testscript_test.go
Expand Up @@ -254,6 +254,29 @@ func TestScripts(t *testing.T) {
},
"echoandexit": echoandexit,
},
Condition: func(cond string) (bool, error) {
// Assume condition name and args are separated by colon (":")
args := strings.Split(cond, ":")
name := args[0]
switch name {
case "is_upper":
if len(args) < 2 {
return false, fmt.Errorf("syntax: [is_upper:word]")
}
return strings.ToUpper(args[1]) == args[1], nil
case "is_lower":
if len(args) < 2 {
return false, fmt.Errorf("syntax: [is_lower:word]")
}
return strings.ToLower(args[1]) == args[1], nil
case "always_true":
return true, nil
case "always_false":
return false, nil
default:
return false, fmt.Errorf("unrecognized condition %s", name)
}
},
Setup: func(env *Env) error {
infos, err := ioutil.ReadDir(env.WorkDir)
if err != nil {
Expand Down