Skip to content

Commit

Permalink
Fix a bunch of errors in impl and interface & start integrating Pigeo…
Browse files Browse the repository at this point in the history
…nhole tests suite
  • Loading branch information
foxcpp committed Jan 28, 2024
1 parent 2b1f0a2 commit 6df5e56
Show file tree
Hide file tree
Showing 22 changed files with 624 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.20

- name: Build
run: go build -v ./...
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/pigeonhole"]
path = tests/pigeonhole
url = https://github.com/dovecot/pigeonhole.git
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ implementation in Go.
See ./cmd/sieve-run.

[RFC 5228]: https://datatracker.ietf.org/doc/html/rfc5228
[RFC 5232]: https://datatracker.ietf.org/doc/html/rfc5232
[RFC 5232]: https://datatracker.ietf.org/doc/html/rfc5232
16 changes: 10 additions & 6 deletions cmd/sieve-run/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ func main() {
}
log.Println("script loaded in", end.Sub(start))

data := sieve.NewRuntimeData(loadedScript, nil, interp.MessageStatic{
SMTPFrom: *envFrom,
SMTPTo: *envTo,
Size: int(fileInfo.Size()),
Header: msgHdr,
})
envData := interp.EnvelopeStatic{
From: *envFrom,
To: *envTo,
}
msgData := interp.MessageStatic{
Size: int(fileInfo.Size()),
Header: msgHdr,
}
data := sieve.NewRuntimeData(loadedScript, interp.DummyPolicy{},
envData, msgData)

ctx := context.Background()
start = time.Now()
Expand Down
38 changes: 38 additions & 0 deletions dovecot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package sieve

import (
"bytes"
"context"
"os"
"path/filepath"
"testing"

"github.com/foxcpp/go-sieve/interp"
)

func RunDovecotTest(t *testing.T, path string) {
svScript, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}

opts := DefaultOptions()
opts.Interp.T = t

script, err := Load(bytes.NewReader(svScript), opts)
if err != nil {
t.Fatal(err)
}

ctx := context.Background()

// Empty data.
data := NewRuntimeData(script, interp.DummyPolicy{},
interp.EnvelopeStatic{}, interp.MessageStatic{})
data.Namespace = os.DirFS(filepath.Dir(path))

err = script.Execute(ctx, data)
if err != nil {
t.Fatal(err)
}
}
16 changes: 10 additions & 6 deletions execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ func testExecute(t *testing.T, in string, eml string, intendedResult result) {
if err != nil {
t.Fatal(err)
}
data := interp.NewRuntimeData(loadedScript, nil, interp.MessageStatic{
SMTPFrom: "[email protected]",
SMTPTo: "[email protected]",
Size: len(eml),
Header: msgHdr,
})
env := interp.EnvelopeStatic{
From: "[email protected]",
To: "[email protected]",
}
msg := interp.MessageStatic{
Size: len(eml),
Header: msgHdr,
}
data := interp.NewRuntimeData(loadedScript, interp.DummyPolicy{},
env, msg)

ctx := context.Background()
if err := loadedScript.Execute(ctx, data); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/foxcpp/go-sieve

go 1.18
go 1.20

require github.com/davecgh/go-spew v1.1.1
159 changes: 159 additions & 0 deletions interp/dovecot_testsuite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package interp

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io/fs"
"net/textproto"
"strings"
"testing"

"github.com/foxcpp/go-sieve/lexer"
"github.com/foxcpp/go-sieve/parser"
)

const DovecotTestExtension = "vnd.dovecot.testsuite"

type CmdDovecotTest struct {
TestName string
Cmds []Cmd
}

func (c CmdDovecotTest) Execute(ctx context.Context, d *RuntimeData) error {
testData := d.Copy()
testData.testName = c.TestName

d.Script.opts.T.Run(c.TestName, func(t *testing.T) {
for _, cmd := range c.Cmds {
if err := cmd.Execute(ctx, testData); err != nil {
if errors.Is(err, ErrStop) {
if testData.testFailMessage != "" {
t.Error("test_fail called:", testData.testFailMessage)
}
return
}
t.Fatal("Test execution error:", err)
}
}
})

return nil
}

type CmdDovecotTestFail struct {
Message string
}

func (c CmdDovecotTestFail) Execute(ctx context.Context, d *RuntimeData) error {
d.testFailMessage = c.Message
return nil
}

type CmdDovecotTestSet struct {
VariableName string
VariableValue string
}

func (c CmdDovecotTestSet) Execute(ctx context.Context, d *RuntimeData) error {
switch c.VariableName {
case "message":
r := textproto.NewReader(bufio.NewReader(strings.NewReader(c.VariableValue)))
msgHdr, err := r.ReadMIMEHeader()
if err != nil {
return fmt.Errorf("failed to parse test message: %v", err)
}

d.Msg = MessageStatic{
Size: len(c.VariableValue),
Header: msgHdr,
}
case "envelope.from":
d.Envelope = EnvelopeStatic{
From: c.VariableValue,
To: d.Envelope.EnvelopeTo(),
}
case "envelope.to":
d.Envelope = EnvelopeStatic{
From: d.Envelope.EnvelopeFrom(),
To: c.VariableValue,
}
default:
d.Variables[c.VariableName] = c.VariableValue
}

return nil
}

type TestDovecotCompile struct {
ScriptPath string
}

func (t TestDovecotCompile) Check(ctx context.Context, d *RuntimeData) (bool, error) {
if d.Namespace == nil {
return false, fmt.Errorf("RuntimeData.Namespace is not set, cannot load scripts")
}

svScript, err := fs.ReadFile(d.Namespace, t.ScriptPath)
if err != nil {
return false, nil
}

toks, err := lexer.Lex(bytes.NewReader(svScript), &lexer.Options{
MaxTokens: 5000,
})
if err != nil {
return false, nil
}

cmds, err := parser.Parse(lexer.NewStream(toks), &parser.Options{
MaxBlockNesting: d.testMaxNesting,
MaxTestNesting: d.testMaxNesting,
})
if err != nil {
return false, nil
}

script, err := LoadScript(cmds, &Options{
MaxRedirects: d.Script.opts.MaxRedirects,
})
if err != nil {
return false, nil
}

d.testScript = script
return true, nil
}

type TestDovecotRun struct {
}

func (t TestDovecotRun) Check(ctx context.Context, d *RuntimeData) (bool, error) {
if d.testScript == nil {
return false, nil
}

testD := d.Copy()
testD.Script = d.testScript
// Note: Loaded script has no test environment available -
// it is a regular Sieve script.

err := d.testScript.Execute(ctx, testD)
if err != nil {
return false, nil
}

return true, nil
}

type TestDovecotTestError struct {
}

func (t TestDovecotTestError) Check(ctx context.Context, d *RuntimeData) (bool, error) {
// go-sieve has a very different error formatting and stops lexing/parsing/loading
// on first error, therefore we skip all test_errors checks as they are
// Pigeonhole-specific.
return true, nil
}
33 changes: 33 additions & 0 deletions interp/load.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package interp

import (
"context"
"strings"

"github.com/foxcpp/go-sieve/lexer"
Expand Down Expand Up @@ -43,6 +44,21 @@ func init() {
"setflag": loadSetFlag, // imap4flags extension
"addflag": loadAddFlag, // imap4flags extension
"removeflag": loadRemoveFlag, // imap4flags extension
// vnd.dovecot.testsuite
"test": loadDovecotTest,
"test_set": loadDovecotTestSet,
"test_fail": loadDovecotTestFail,
"test_binary_load": loadNoop, // go-sieve has no intermediate binary representation
"test_binary_save": loadNoop, // go-sieve has no intermediate binary representation
// "test_result_execute" // apply script results (validated using test_message)
// "test_mailbox_create"
// "test_imap_metadata_set"
// "test_config_reload"
// "test_config_set"
// "test_config_unset"
// "test_result_reset"
// "test_message"

}
tests = map[string]func(*Script, parser.Test) (Test, error){
// RFC 5228 Tests
Expand All @@ -56,6 +72,13 @@ func init() {
"header": loadHeaderTest,
"not": loadNotTest,
"size": loadSizeTest,
// vnd.dovecot.testsuite
"test_script_compile": loadDovecotCompile, // compile script (to test for compile errors)
"test_script_run": loadDovecotRun, // run script (to test for run-time errors)
"test_error": loadDovecotError, // check detailed results of test_script_compile or test_script_run
// "test_message" // check results of test_result_execute - where messages are
// "test_result_action" // check results of test_result_execute - what actions are executed
// "test_result_reset" // clean results as observed by test_result_action
}
}

Expand Down Expand Up @@ -107,3 +130,13 @@ func LoadTest(s *Script, t parser.Test) (Test, error) {
}
return factory(s, t)
}

type CmdNoop struct{}

func (c CmdNoop) Execute(_ context.Context, _ *RuntimeData) error {
return nil
}

func loadNoop(_ *Script, _ parser.Cmd) (Cmd, error) {
return CmdNoop{}, nil
}
8 changes: 8 additions & 0 deletions interp/load_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func loadRequire(s *Script, pcmd parser.Cmd) (Cmd, error) {
}

for _, ext := range exts {
if ext == DovecotTestExtension {
if s.opts.T == nil {
return nil, fmt.Errorf("testing environment is not available, cannot use vnd.dovecot.testsuite")
}
s.extensions[DovecotTestExtension] = struct{}{}
continue
}

if _, ok := supportedRequires[ext]; !ok {
return nil, fmt.Errorf("loadRequire: unsupported extension: %v", ext)
}
Expand Down
Loading

0 comments on commit 6df5e56

Please sign in to comment.