Skip to content

Commit

Permalink
feat: add unit tests to p/gov/proposal (gnolang#2475)
Browse files Browse the repository at this point in the history
## Description

This PR adds additional unit tests to the `p/gov/proposal` package, and
tidies the code a bit.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Manfred Touron <[email protected]>
  • Loading branch information
2 people authored and gfanton committed Jul 23, 2024
1 parent ac22593 commit ae14a5b
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 20 deletions.
2 changes: 2 additions & 0 deletions examples/gno.land/p/gov/proposal/gno.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module gno.land/p/gov/proposal

require gno.land/p/demo/uassert v0.0.0-latest
31 changes: 21 additions & 10 deletions examples/gno.land/p/gov/proposal/proposal.gno
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Package proposal provides a structure for executing proposals.
package proposal

import "std"
import (
"errors"
"std"
)

const daoPkgPath = "gno.land/r/gov/dao" // XXX: make it configurable with r/sys/vars?
var errNotGovDAO = errors.New("only r/gov/dao can be the caller")

// NewExecutor creates a new executor with the provided callback function.
func NewExecutor(callback func() error) Executor {
Expand All @@ -20,42 +23,50 @@ type executorImpl struct {
success bool
}

// execute runs the executor's callback function.
// Execute runs the executor's callback function.
func (exec *executorImpl) Execute() error {
if exec.done {
return ErrAlreadyDone
}

// Verify the executor is r/gov/dao
assertCalledByGovdao()

// Run the callback
err := exec.callback()

exec.done = true
exec.success = err == nil

return err
}

// Done returns whether the executor has been executed.
func (exec *executorImpl) Done() bool {
// IsDone returns whether the executor has been executed.
func (exec *executorImpl) IsDone() bool {
return exec.done
}

// Success returns whether the execution was successful.
func (exec *executorImpl) Success() bool {
// IsSuccessful returns whether the execution was successful.
func (exec *executorImpl) IsSuccessful() bool {
return exec.success
}

func (exec executorImpl) Status() Status {
func (exec executorImpl) GetStatus() Status {
switch {
case exec.success:
return Success
return Succeeded
case exec.done:
return Failed
default:
return NotExecuted
}
}

// assertCalledByGovdao asserts that the calling Realm is /r/gov/dao
func assertCalledByGovdao() {
caller := std.CurrentRealm().PkgPath()

if caller != daoPkgPath {
panic("only gov/dao can execute proposals")
panic(errNotGovDAO)
}
}
173 changes: 173 additions & 0 deletions examples/gno.land/p/gov/proposal/proposal_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package proposal

import (
"errors"
"std"
"testing"

"gno.land/p/demo/uassert"
)

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

verifyProposalFailed := func(e Executor) {
uassert.True(t, e.IsDone(), "expected proposal to be done")
uassert.False(t, e.IsSuccessful(), "expected proposal to fail")

if e.GetStatus() != Failed {
t.Fatal("expected status to be Failed")
}
}

verifyProposalSucceeded := func(e Executor) {
uassert.True(t, e.IsDone(), "expected proposal to be done")
uassert.True(t, e.IsSuccessful(), "expected proposal to be successful")

if e.GetStatus() != Succeeded {
t.Fatal("expected status to be Succeeded")
}
}

t.Run("govdao not caller", func(t *testing.T) {
t.Parallel()

var (
called = false

cb = func() error {
called = true

return nil
}
)

// Create the executor
e := NewExecutor(cb)

if e.GetStatus() != NotExecuted {
t.Fatal("expected status to be NotExecuted")
}

// Execute as not the /r/gov/dao caller
uassert.PanicsWithMessage(t, errNotGovDAO.Error(), func() {
_ = e.Execute()
})

uassert.False(t, called, "expected proposal to not execute")
})

t.Run("execution successful", func(t *testing.T) {
t.Parallel()

var (
called = false

cb = func() error {
called = true

return nil
}
)

// Create the executor
e := NewExecutor(cb)

if e.GetStatus() != NotExecuted {
t.Fatal("expected status to be NotExecuted")
}

// Execute as the /r/gov/dao caller
r := std.NewCodeRealm(daoPkgPath)
std.TestSetRealm(r)

uassert.NotPanics(t, func() {
err := e.Execute()

uassert.NoError(t, err)
})

uassert.True(t, called, "expected proposal to execute")

// Make sure the execution params are correct
verifyProposalSucceeded(e)
})

t.Run("execution unsuccessful", func(t *testing.T) {
t.Parallel()

var (
called = false
expectedErr = errors.New("unexpected")

cb = func() error {
called = true

return expectedErr
}
)

// Create the executor
e := NewExecutor(cb)

if e.GetStatus() != NotExecuted {
t.Fatal("expected status to be NotExecuted")
}

// Execute as the /r/gov/dao caller
r := std.NewCodeRealm(daoPkgPath)
std.TestSetRealm(r)

uassert.NotPanics(t, func() {
err := e.Execute()

uassert.ErrorIs(t, err, expectedErr)
})

uassert.True(t, called, "expected proposal to execute")

// Make sure the execution params are correct
verifyProposalFailed(e)
})

t.Run("proposal already executed", func(t *testing.T) {
t.Parallel()

var (
called = false

cb = func() error {
called = true

return nil
}
)

// Create the executor
e := NewExecutor(cb)

if e.GetStatus() != NotExecuted {
t.Fatal("expected status to be NotExecuted")
}

// Execute as the /r/gov/dao caller
r := std.NewCodeRealm(daoPkgPath)
std.TestSetRealm(r)

uassert.NotPanics(t, func() {
uassert.NoError(t, e.Execute())
})

uassert.True(t, called, "expected proposal to execute")

// Make sure the execution params are correct
verifyProposalSucceeded(e)

// Attempt to execute the proposal again
uassert.NotPanics(t, func() {
err := e.Execute()

uassert.ErrorIs(t, err, ErrAlreadyDone)
})
})
}
22 changes: 17 additions & 5 deletions examples/gno.land/p/gov/proposal/types.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ import "errors"
// Executor represents a minimal closure-oriented proposal design.
// It is intended to be used by a govdao governance proposal (v1, v2, etc).
type Executor interface {
// Execute executes the given proposal, and returns any error encountered
// during the execution
Execute() error
Done() bool
Success() bool // Done() && !err
Status() Status // human-readable execution status

// IsDone returns a flag indicating if the proposal was executed
IsDone() bool

// IsSuccessful returns a flag indicating if the proposal was executed
// and is successful
IsSuccessful() bool // IsDone() && !err

// GetStatus returns the current human-readable status
// of the proposal
GetStatus() Status // human-readable execution status
}

// ErrAlreadyDone is the error returned when trying to execute an already
Expand All @@ -19,8 +29,10 @@ var ErrAlreadyDone = errors.New("already executed")
// Status enum.
type Status string

var (
const (
NotExecuted Status = "not_executed"
Success Status = "success"
Succeeded Status = "succeeded"
Failed Status = "failed"
)

const daoPkgPath = "gno.land/r/gov/dao" // TODO: make sure this is configurable through r/sys/vars
4 changes: 2 additions & 2 deletions examples/gno.land/r/gov/dao/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type proposal struct {
}

func (p proposal) Status() Status {
if p.executor.Done() {
return Status(p.executor.Status())
if p.executor.IsDone() {
return Status(p.executor.GetStatus())
}
if p.accepted {
return Accepted
Expand Down
2 changes: 1 addition & 1 deletion examples/gno.land/r/gov/dao/prop1_filetest.gno
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func main() {
// # Prop#0
//
// manual valset changes proposal example
// Status: success
// Status: succeeded
// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm
// --
// Valset changes to apply:
Expand Down
2 changes: 2 additions & 0 deletions gnovm/stdlibs/math/rand/exp.gno
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ var ke = [256]uint32{
0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d,
0xe6da6ecf,
}

var we = [256]float32{
2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11,
3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11,
Expand Down Expand Up @@ -165,6 +166,7 @@ var we = [256]float32{
1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09,
1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09,
}

var fe = [256]float32{
1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933,
0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686,
Expand Down
2 changes: 2 additions & 0 deletions gnovm/stdlibs/math/rand/normal.gno
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var kn = [128]uint32{
0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a,
0x7ba90bdc, 0x7a722176, 0x77d664e5,
}

var wn = [128]float32{
1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10,
2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10,
Expand Down Expand Up @@ -127,6 +128,7 @@ var wn = [128]float32{
1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09,
1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09,
}

var fn = [128]float32{
1, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303,
0.8555006, 0.8387836, 0.8229072, 0.8077383, 0.793177,
Expand Down
5 changes: 3 additions & 2 deletions tm2/pkg/libtm/messages/types/messages.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ae14a5b

Please sign in to comment.