maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19// Copyright 2015 Light Code Labs, LLC
 20//
 21// Licensed under the Apache License, Version 2.0 (the "License");
 22// you may not use this file except in compliance with the License.
 23// You may obtain a copy of the License at
 24//
 25//     http://www.apache.org/licenses/LICENSE-2.0
 26//
 27// Unless required by applicable law or agreed to in writing, software
 28// distributed under the License is distributed on an "AS IS" BASIS,
 29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 30// See the License for the specific language governing permissions and
 31// limitations under the License.
 32
 33package lexer
 34
 35import (
 36	"reflect"
 37	"strings"
 38	"testing"
 39)
 40
 41func TestDispenser_Val_Next(t *testing.T) {
 42	input := `host:port
 43			  dir1 arg1
 44			  dir2 arg2 arg3
 45			  dir3`
 46	d := NewDispenser("Testfile", strings.NewReader(input))
 47
 48	if val := d.Val(); val != "" {
 49		t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
 50	}
 51
 52	assertNext := func(shouldLoad bool, expectedCursor int, expectedVal string) {
 53		if loaded := d.Next(); loaded != shouldLoad {
 54			t.Errorf("Next(): Expected %v but got %v instead (val '%s')", shouldLoad, loaded, d.Val())
 55		}
 56		if d.cursor != expectedCursor {
 57			t.Errorf("Expected cursor to be %d, but was %d", expectedCursor, d.cursor)
 58		}
 59		if d.nesting != 0 {
 60			t.Errorf("Nesting should be 0, was %d instead", d.nesting)
 61		}
 62		if val := d.Val(); val != expectedVal {
 63			t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
 64		}
 65	}
 66
 67	assertNext(true, 0, "host:port")
 68	assertNext(true, 1, "dir1")
 69	assertNext(true, 2, "arg1")
 70	assertNext(true, 3, "dir2")
 71	assertNext(true, 4, "arg2")
 72	assertNext(true, 5, "arg3")
 73	assertNext(true, 6, "dir3")
 74	// Note: This next test simply asserts existing behavior.
 75	// If desired, we may wish to empty the token value after
 76	// reading past the EOF. Open an issue if you want this change.
 77	assertNext(false, 6, "dir3")
 78}
 79
 80func TestDispenser_NextArg(t *testing.T) {
 81	input := `dir1 arg1
 82			  dir2 arg2 arg3
 83			  dir3`
 84	d := NewDispenser("Testfile", strings.NewReader(input))
 85
 86	assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
 87		if d.Next() != shouldLoad {
 88			t.Errorf("Next(): Should load token but got false instead (val: '%s')", d.Val())
 89		}
 90		if d.cursor != expectedCursor {
 91			t.Errorf("Next(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
 92		}
 93		if val := d.Val(); val != expectedVal {
 94			t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
 95		}
 96	}
 97
 98	assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) {
 99		if !d.NextArg() {
100			t.Error("NextArg(): Should load next argument but got false instead")
101		}
102		if d.cursor != expectedCursor {
103			t.Errorf("NextArg(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
104		}
105		if val := d.Val(); val != expectedVal {
106			t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
107		}
108		if !loadAnother {
109			if d.NextArg() {
110				t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val())
111			}
112			if d.cursor != expectedCursor {
113				t.Errorf("NextArg(): Expected cursor to remain at %d, but it was %d", expectedCursor, d.cursor)
114			}
115		}
116	}
117
118	assertNext(true, "dir1", 0)
119	assertNextArg("arg1", false, 1)
120	assertNext(true, "dir2", 2)
121	assertNextArg("arg2", true, 3)
122	assertNextArg("arg3", false, 4)
123	assertNext(true, "dir3", 5)
124	assertNext(false, "dir3", 5)
125}
126
127func TestDispenser_NextLine(t *testing.T) {
128	input := `host:port
129			  dir1 arg1
130			  dir2 arg2 arg3`
131	d := NewDispenser("Testfile", strings.NewReader(input))
132
133	assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
134		if d.NextLine() != shouldLoad {
135			t.Errorf("NextLine(): Should load token but got false instead (val: '%s')", d.Val())
136		}
137		if d.cursor != expectedCursor {
138			t.Errorf("NextLine(): Expected cursor to be %d, instead was %d", expectedCursor, d.cursor)
139		}
140		if val := d.Val(); val != expectedVal {
141			t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
142		}
143	}
144
145	assertNextLine(true, "host:port", 0)
146	assertNextLine(true, "dir1", 1)
147	assertNextLine(false, "dir1", 1)
148	d.Next() // arg1
149	assertNextLine(true, "dir2", 3)
150	assertNextLine(false, "dir2", 3)
151	d.Next() // arg2
152	assertNextLine(false, "arg2", 4)
153	d.Next() // arg3
154	assertNextLine(false, "arg3", 5)
155}
156
157func TestDispenser_NextBlock(t *testing.T) {
158	input := `foobar1 {
159			  	sub1 arg1
160			  	sub2
161			  }
162			  foobar2 {
163			  }`
164	d := NewDispenser("Testfile", strings.NewReader(input))
165
166	assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
167		if loaded := d.NextBlock(); loaded != shouldLoad {
168			t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
169		}
170		if d.cursor != expectedCursor {
171			t.Errorf("NextBlock(): Expected cursor to be %d, was %d", expectedCursor, d.cursor)
172		}
173		if d.nesting != expectedNesting {
174			t.Errorf("NextBlock(): Nesting should be %d, not %d", expectedNesting, d.nesting)
175		}
176	}
177
178	assertNextBlock(false, -1, 0)
179	d.Next() // foobar1
180	assertNextBlock(true, 2, 1)
181	assertNextBlock(true, 3, 1)
182	assertNextBlock(true, 4, 1)
183	assertNextBlock(false, 5, 0)
184	d.Next()                     // foobar2
185	assertNextBlock(false, 8, 0) // empty block is as if it didn't exist
186}
187
188func TestDispenser_Args(t *testing.T) {
189	var s1, s2, s3 string
190	input := `dir1 arg1 arg2 arg3
191			  dir2 arg4 arg5
192			  dir3 arg6 arg7
193			  dir4`
194	d := NewDispenser("Testfile", strings.NewReader(input))
195
196	d.Next() // dir1
197
198	// As many strings as arguments
199	if all := d.Args(&s1, &s2, &s3); !all {
200		t.Error("Args(): Expected true, got false")
201	}
202	if s1 != "arg1" {
203		t.Errorf("Args(): Expected s1 to be 'arg1', got '%s'", s1)
204	}
205	if s2 != "arg2" {
206		t.Errorf("Args(): Expected s2 to be 'arg2', got '%s'", s2)
207	}
208	if s3 != "arg3" {
209		t.Errorf("Args(): Expected s3 to be 'arg3', got '%s'", s3)
210	}
211
212	d.Next() // dir2
213
214	// More strings than arguments
215	if all := d.Args(&s1, &s2, &s3); all {
216		t.Error("Args(): Expected false, got true")
217	}
218	if s1 != "arg4" {
219		t.Errorf("Args(): Expected s1 to be 'arg4', got '%s'", s1)
220	}
221	if s2 != "arg5" {
222		t.Errorf("Args(): Expected s2 to be 'arg5', got '%s'", s2)
223	}
224	if s3 != "arg3" {
225		t.Errorf("Args(): Expected s3 to be unchanged ('arg3'), instead got '%s'", s3)
226	}
227
228	// (quick cursor check just for kicks and giggles)
229	if d.cursor != 6 {
230		t.Errorf("Cursor should be 6, but is %d", d.cursor)
231	}
232
233	d.Next() // dir3
234
235	// More arguments than strings
236	if all := d.Args(&s1); !all {
237		t.Error("Args(): Expected true, got false")
238	}
239	if s1 != "arg6" {
240		t.Errorf("Args(): Expected s1 to be 'arg6', got '%s'", s1)
241	}
242
243	d.Next() // dir4
244
245	// No arguments or strings
246	if all := d.Args(); !all {
247		t.Error("Args(): Expected true, got false")
248	}
249
250	// No arguments but at least one string
251	if all := d.Args(&s1); all {
252		t.Error("Args(): Expected false, got true")
253	}
254}
255
256func TestDispenser_RemainingArgs(t *testing.T) {
257	input := `dir1 arg1 arg2 arg3
258			  dir2 arg4 arg5
259			  dir3 arg6 { arg7
260			  dir4`
261	d := NewDispenser("Testfile", strings.NewReader(input))
262
263	d.Next() // dir1
264
265	args := d.RemainingArgs()
266	if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(args, expected) {
267		t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
268	}
269
270	d.Next() // dir2
271
272	args = d.RemainingArgs()
273	if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) {
274		t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
275	}
276
277	d.Next() // dir3
278
279	args = d.RemainingArgs()
280	if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) {
281		t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
282	}
283
284	d.Next() // {
285	d.Next() // arg7
286	d.Next() // dir4
287
288	args = d.RemainingArgs()
289	if len(args) != 0 {
290		t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args)
291	}
292}
293
294func TestDispenser_ArgErr_Err(t *testing.T) {
295	input := `dir1 {
296			  }
297			  dir2 arg1 arg2`
298	d := NewDispenser("Testfile", strings.NewReader(input))
299
300	d.cursor = 1 // {
301
302	if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "{") {
303		t.Errorf("ArgErr(): Expected an error message with { in it, but got '%v'", err)
304	}
305
306	d.cursor = 5 // arg2
307
308	if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "arg2") {
309		t.Errorf("ArgErr(): Expected an error message with 'arg2' in it; got '%v'", err)
310	}
311
312	err := d.Err("foobar")
313	if err == nil {
314		t.Fatalf("Err(): Expected an error, got nil")
315	}
316
317	if !strings.Contains(err.Error(), "Testfile:3") {
318		t.Errorf("Expected error message with filename:line in it; got '%v'", err)
319	}
320
321	if !strings.Contains(err.Error(), "foobar") {
322		t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
323	}
324}