diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 08d4d24..1dafe4c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -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 ./... diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a12e151 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/pigeonhole"] + path = tests/pigeonhole + url = https://github.com/dovecot/pigeonhole.git diff --git a/README.md b/README.md index 8c42ca1..ca18df4 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file +[RFC 5232]: https://datatracker.ietf.org/doc/html/rfc5232 diff --git a/cmd/sieve-run/main.go b/cmd/sieve-run/main.go index 15cdd57..567b30a 100644 --- a/cmd/sieve-run/main.go +++ b/cmd/sieve-run/main.go @@ -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() diff --git a/dovecot.go b/dovecot.go new file mode 100644 index 0000000..9d23b5b --- /dev/null +++ b/dovecot.go @@ -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) + } +} diff --git a/execute_test.go b/execute_test.go index d46e7e1..e4740a7 100644 --- a/execute_test.go +++ b/execute_test.go @@ -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: "from@test.com", - SMTPTo: "to@test.com", - Size: len(eml), - Header: msgHdr, - }) + env := interp.EnvelopeStatic{ + From: "from@test.com", + To: "to@test.com", + } + 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 { diff --git a/go.mod b/go.mod index 58ed6a1..954c7a1 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/interp/dovecot_testsuite.go b/interp/dovecot_testsuite.go new file mode 100644 index 0000000..6322688 --- /dev/null +++ b/interp/dovecot_testsuite.go @@ -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 +} diff --git a/interp/load.go b/interp/load.go index edf22f9..343983e 100644 --- a/interp/load.go +++ b/interp/load.go @@ -1,6 +1,7 @@ package interp import ( + "context" "strings" "github.com/foxcpp/go-sieve/lexer" @@ -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 @@ -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 } } @@ -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 +} diff --git a/interp/load_control.go b/interp/load_control.go index 48dc28e..3ca5d2a 100644 --- a/interp/load_control.go +++ b/interp/load_control.go @@ -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) } diff --git a/interp/load_dovecot.go b/interp/load_dovecot.go new file mode 100644 index 0000000..1176774 --- /dev/null +++ b/interp/load_dovecot.go @@ -0,0 +1,102 @@ +package interp + +import ( + "fmt" + + "github.com/foxcpp/go-sieve/parser" +) + +func loadDovecotTestSet(s *Script, pcmd parser.Cmd) (Cmd, error) { + if !s.RequiresExtension(DovecotTestExtension) || s.opts.T == nil { + return nil, fmt.Errorf("testing environment is not enabled") + } + cmd := CmdDovecotTestSet{} + err := LoadSpec(s, &Spec{ + Pos: []SpecPosArg{ + { + MinStrCount: 1, + MaxStrCount: 1, + MatchStr: func(val []string) { + cmd.VariableName = val[0] + }, + }, + { + MinStrCount: 1, + MaxStrCount: 1, + MatchStr: func(val []string) { + cmd.VariableValue = val[0] + }, + }, + }, + }, pcmd.Position, pcmd.Args, pcmd.Tests, pcmd.Block) + return cmd, err +} + +func loadDovecotTestFail(s *Script, pcmd parser.Cmd) (Cmd, error) { + if !s.RequiresExtension(DovecotTestExtension) || s.opts.T == nil { + return nil, fmt.Errorf("testing environment is not enabled") + } + cmd := CmdDovecotTestFail{} + err := LoadSpec(s, &Spec{ + Pos: []SpecPosArg{ + { + MinStrCount: 1, + MaxStrCount: 1, + MatchStr: func(val []string) { + cmd.Message = val[0] + }, + }, + }, + }, pcmd.Position, pcmd.Args, pcmd.Tests, pcmd.Block) + return cmd, err +} + +func loadDovecotTest(s *Script, pcmd parser.Cmd) (Cmd, error) { + if !s.RequiresExtension(DovecotTestExtension) || s.opts.T == nil { + return nil, fmt.Errorf("testing environment is not enabled") + } + cmd := CmdDovecotTest{} + err := LoadSpec(s, &Spec{ + Pos: []SpecPosArg{ + { + MinStrCount: 1, + MaxStrCount: 1, + MatchStr: func(val []string) { + cmd.TestName = val[0] + }, + }, + }, + AddBlock: func(cmds []Cmd) { + cmd.Cmds = cmds + }, + }, pcmd.Position, pcmd.Args, pcmd.Tests, pcmd.Block) + return cmd, err +} + +func loadDovecotCompile(s *Script, test parser.Test) (Test, error) { + loaded := TestDovecotCompile{} + err := LoadSpec(s, &Spec{ + Pos: []SpecPosArg{ + { + MatchStr: func(val []string) { + loaded.ScriptPath = val[0] + }, + MinStrCount: 1, + MaxStrCount: 1, + }, + }, + }, test.Position, test.Args, test.Tests, nil) + return loaded, err +} + +func loadDovecotRun(s *Script, test parser.Test) (Test, error) { + loaded := TestDovecotRun{} + err := LoadSpec(s, &Spec{}, test.Position, test.Args, test.Tests, nil) + return loaded, err +} + +func loadDovecotError(s *Script, test parser.Test) (Test, error) { + loaded := TestDovecotTestError{} + err := LoadSpec(s, &Spec{}, test.Position, test.Args, test.Tests, nil) + return loaded, err +} diff --git a/interp/message_static.go b/interp/message_static.go index 31518e1..199c20c 100644 --- a/interp/message_static.go +++ b/interp/message_static.go @@ -22,29 +22,33 @@ var ( _ MessageHeader = textproto.MIMEHeader{} ) -// MessageStatic is a simple Message interface implementation -// that just keeps all data in memory in a Go struct. -type MessageStatic struct { - SMTPFrom string - SMTPTo string - Size int - Header MessageHeader +type EnvelopeStatic struct { + From string + To string + Auth string +} + +func (m EnvelopeStatic) EnvelopeFrom() string { + return m.From } -func (m MessageStatic) EnvelopeFrom() string { - return m.SMTPFrom +func (m EnvelopeStatic) EnvelopeTo() string { + return m.To } -func (m MessageStatic) EnvelopeTo() string { - return m.SMTPTo +func (m EnvelopeStatic) AuthUsername() string { + return m.Auth +} + +// MessageStatic is a simple Message interface implementation +// that just keeps all data in memory in a Go struct. +type MessageStatic struct { + Size int + Header MessageHeader } -func (m MessageStatic) HeaderGet(key string) (string, bool, error) { - values := m.Header.Values(key) - if len(values) == 0 { - return "", false, nil - } - return values[0], true, nil +func (m MessageStatic) HeaderGet(key string) ([]string, error) { + return m.Header.Values(key), nil } func (m MessageStatic) MessageSize() int { diff --git a/interp/runtime.go b/interp/runtime.go index 55a3e2c..ba64ed6 100644 --- a/interp/runtime.go +++ b/interp/runtime.go @@ -1,35 +1,48 @@ package interp -import "context" +import ( + "context" + "io/fs" +) type PolicyReader interface { RedirectAllowed(ctx context.Context, d *RuntimeData, addr string) (bool, error) } -type Message interface { +type Envelope interface { EnvelopeFrom() string EnvelopeTo() string - - HeaderGet(key string) (string, bool, error) - - MessageSize() int + AuthUsername() string } -type Callback struct { - RedirectAllowed func(ctx context.Context, d *RuntimeData, addr string) (bool, error) - HeaderGet func(value string) (string, bool, error) -} +type Message interface { + /* + HeaderGet returns the header field value. + + RFC requires the following handling for encoded fields: -type SMTPEnvelope struct { - From string - To string + Comparisons are performed on octets. Implementations convert text + from header fields in all charsets [MIME3] to Unicode, encoded as + UTF-8, as input to the comparator (see section 2.7.3). + Implementations MUST be capable of converting US-ASCII, ISO-8859- + 1, the US-ASCII subset of ISO-8859-* character sets, and UTF-8. + Text that the implementation cannot convert to Unicode for any + reason MAY be treated as plain US-ASCII (including any [MIME3] + syntax) or processed according to local conventions. An encoded + NUL octet (character zero) SHOULD NOT cause early termination of + the header content being compared against. + */ + HeaderGet(key string) ([]string, error) + MessageSize() int } type RuntimeData struct { Policy PolicyReader + Envelope Envelope Msg Message Script *Script - Callback Callback + // For files accessible vis "include", "test_script_compile", etc. + Namespace fs.FS ifResult bool @@ -40,14 +53,58 @@ type RuntimeData struct { ImplicitKeep bool FlagAliases map[string]string + + Variables map[string]string + + // vnd.dovecot.testsuit state + testName string + testFailMessage string // if set - test failed. + testScript *Script // script loaded using test_script_compile + testMaxNesting int // max nesting for scripts loaded using test_script_compile +} + +func (d *RuntimeData) Copy() *RuntimeData { + newData := &RuntimeData{ + Policy: d.Policy, + Envelope: d.Envelope, + Msg: d.Msg, + Script: d.Script, + Namespace: d.Namespace, + RedirectAddr: make([]string, len(d.RedirectAddr)), + Mailboxes: make([]string, len(d.Mailboxes)), + Flags: make([]string, len(d.Flags)), + Keep: d.Keep, + ImplicitKeep: d.ImplicitKeep, + FlagAliases: make(map[string]string, len(d.FlagAliases)), + Variables: make(map[string]string, len(d.Variables)), + testName: d.testName, + testFailMessage: d.testFailMessage, + testScript: d.testScript, + testMaxNesting: d.testMaxNesting, + } + + copy(newData.RedirectAddr, d.RedirectAddr) + copy(newData.Mailboxes, d.Mailboxes) + copy(newData.Flags, d.Flags) + + for k, v := range d.FlagAliases { + newData.FlagAliases[k] = v + } + for k, v := range d.Variables { + newData.Variables[k] = v + } + + return newData } -func NewRuntimeData(s *Script, p PolicyReader, m Message) *RuntimeData { +func NewRuntimeData(s *Script, p PolicyReader, e Envelope, m Message) *RuntimeData { return &RuntimeData{ Script: s, Policy: p, + Envelope: e, Msg: m, ImplicitKeep: true, FlagAliases: make(map[string]string), + Variables: map[string]string{}, } } diff --git a/interp/script.go b/interp/script.go index a4dccdb..335ddd1 100644 --- a/interp/script.go +++ b/interp/script.go @@ -3,6 +3,7 @@ package interp import ( "context" "errors" + "testing" ) type Cmd interface { @@ -12,8 +13,9 @@ type Cmd interface { type Options struct { MaxRedirects int - // Enable vnd.dovecot.testsuite extension. Use for testing only. - AllowDovecotTests bool + // If specified - enables vnd.dovecot.testsuite extension + // and will execute tests. + T *testing.T } type Script struct { diff --git a/interp/test.go b/interp/test.go index 650fb76..4d915f1 100644 --- a/interp/test.go +++ b/interp/test.go @@ -36,26 +36,27 @@ func (a AddressTest) Check(ctx context.Context, d *RuntimeData) (bool, error) { continue } - value, ok, err := d.Msg.HeaderGet(hdr) + values, err := d.Msg.HeaderGet(hdr) if err != nil { return false, err } - if !ok { - continue - } - addrList, err := mail.ParseAddressList(value) - if err != nil { - return false, nil - } + // TODO: Reconsider how this works with field decoding - for _, k := range a.Key { - ok, err := testAddress(a.AddressPart, a.Comparator, a.Match, addrList, k) + for _, value := range values { + addrList, err := mail.ParseAddressList(value) if err != nil { - return false, err + return false, nil } - if ok { - return true, nil + + for _, k := range a.Key { + ok, err := testAddress(a.AddressPart, a.Comparator, a.Match, addrList, k) + if err != nil { + return false, err + } + if ok { + return true, nil + } } } } @@ -110,9 +111,11 @@ func (e EnvelopeTest) Check(ctx context.Context, d *RuntimeData) (bool, error) { var value string switch strings.ToLower(field) { case "from": - value = d.Msg.EnvelopeFrom() + value = d.Envelope.EnvelopeFrom() case "to": - value = d.Msg.EnvelopeTo() + value = d.Envelope.EnvelopeTo() + case "auth": + value = d.Envelope.AuthUsername() default: return false, fmt.Errorf("envelope: unsupported envelope-part: %v", field) } @@ -138,11 +141,11 @@ type ExistsTest struct { func (e ExistsTest) Check(ctx context.Context, d *RuntimeData) (bool, error) { for _, field := range e.Fields { - _, ok, err := d.Msg.HeaderGet(field) + values, err := d.Msg.HeaderGet(field) if err != nil { return false, err } - if !ok { + if len(values) == 0 { return false, nil } } @@ -171,21 +174,20 @@ type HeaderTest struct { func (h HeaderTest) Check(ctx context.Context, d *RuntimeData) (bool, error) { for _, hdr := range h.Header { - value, ok, err := d.Msg.HeaderGet(hdr) + values, err := d.Msg.HeaderGet(hdr) if err != nil { return false, err } - if !ok { - continue - } - for _, k := range h.Key { - ok, err := testString(h.Comparator, h.Match, value, k) - if err != nil { - return false, err - } - if ok { - return true, nil + for _, value := range values { + for _, k := range h.Key { + ok, err := testString(h.Comparator, h.Match, value, k) + if err != nil { + return false, err + } + if ok { + return true, nil + } } } } diff --git a/sieve.go b/sieve.go index d1570cd..79ec51a 100644 --- a/sieve.go +++ b/sieve.go @@ -14,6 +14,7 @@ type ( PolicyReader = interp.PolicyReader Message = interp.Message + Envelope = interp.Envelope Options struct { Lexer lexer.Options @@ -51,6 +52,6 @@ func Load(r io.Reader, opts Options) (*Script, error) { return interp.LoadScript(cmds, &opts.Interp) } -func NewRuntimeData(s *Script, p interp.PolicyReader, msg interp.Message) *interp.RuntimeData { - return interp.NewRuntimeData(s, p, msg) +func NewRuntimeData(s *Script, p interp.PolicyReader, e interp.Envelope, msg interp.Message) *interp.RuntimeData { + return interp.NewRuntimeData(s, p, e, msg) } diff --git a/tests/base_test.go b/tests/base_test.go new file mode 100644 index 0000000..88a8aa4 --- /dev/null +++ b/tests/base_test.go @@ -0,0 +1,50 @@ +package tests + +import ( + "path/filepath" + "testing" + + "github.com/foxcpp/go-sieve" +) + +func TestTestsuite(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "testsuite.svtest")) +} + +func TestLexer(t *testing.T) { + t.Skip("requires variables extension") + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "lexer.svtest")) +} + +func TestControlIf(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "control-if.svtest")) +} + +func TestControlStop(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "control-stop.svtest")) +} + +func TestTestAddress(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-address.svtest")) +} + +func TestTestAllof(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-allof.svtest")) +} + +func TestTestAnyof(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-anyof.svtest")) +} + +func TestTestExists(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-exists.svtest")) +} + +func TestTestHeader(t *testing.T) { + t.Skip("requires variables extension") + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-header.svtest")) +} + +func TestTestSize(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "test-size.svtest")) +} diff --git a/tests/comparators_test.go b/tests/comparators_test.go new file mode 100644 index 0000000..000de25 --- /dev/null +++ b/tests/comparators_test.go @@ -0,0 +1,16 @@ +package tests + +import ( + "path/filepath" + "testing" + + "github.com/foxcpp/go-sieve" +) + +func TestComparatorsOctet(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "comparators", "i-octet.svtest")) +} + +func TestComparatorsASCIICasemap(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "comparators", "i-ascii-casemap.svtest")) +} diff --git a/tests/compile_test.go b/tests/compile_test.go new file mode 100644 index 0000000..b126cfb --- /dev/null +++ b/tests/compile_test.go @@ -0,0 +1,29 @@ +package tests + +import ( + "path/filepath" + "testing" + + "github.com/foxcpp/go-sieve" +) + +func TestCompile(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "compile", "compile.svtest")) +} + +// go-sieve has more simple error handling, but we still run +// tests to check whether any invalid scripts are not loaded as valid. + +func TestCompileErrors(t *testing.T) { + t.Skip("requires relational extension") + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "compile", "errors.svtest")) +} + +func TestCompileRecover(t *testing.T) { + t.Skip("requires relational extension") + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "compile", "recover.svtest")) +} + +func TestCompileWarnings(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "compile", "warnings.svtest")) +} diff --git a/tests/envelope_test.go b/tests/envelope_test.go new file mode 100644 index 0000000..5d98551 --- /dev/null +++ b/tests/envelope_test.go @@ -0,0 +1,12 @@ +package tests + +import ( + "path/filepath" + "testing" + + "github.com/foxcpp/go-sieve" +) + +func TestExtensionsEnvelope(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "extensions", "envelope.svtest")) +} diff --git a/tests/match_test.go b/tests/match_test.go new file mode 100644 index 0000000..94afa8f --- /dev/null +++ b/tests/match_test.go @@ -0,0 +1,20 @@ +package tests + +import ( + "path/filepath" + "testing" + + "github.com/foxcpp/go-sieve" +) + +func TestMatchContains(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "match-types", "contains.svtest")) +} + +func TestMatchIs(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "match-types", "is.svtest")) +} + +func TestMatchMatches(t *testing.T) { + sieve.RunDovecotTest(t, filepath.Join("pigeonhole", "tests", "match-types", "matches.svtest")) +} diff --git a/tests/pigeonhole b/tests/pigeonhole new file mode 160000 index 0000000..7fd33fd --- /dev/null +++ b/tests/pigeonhole @@ -0,0 +1 @@ +Subproject commit 7fd33fd48a32d6fb136f22c3acd476327fdb4363