1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819// Copyright 2015 Light Code Labs, LLC20//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 at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// 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 and31// limitations under the License.3233package lexer3435import (36 "reflect"37 "strings"38 "testing"39)4041func TestDispenser_Val_Next(t *testing.T) {42 input := `host:port43 dir1 arg144 dir2 arg2 arg345 dir3`46 d := NewDispenser("Testfile", strings.NewReader(input))4748 if val := d.Val(); val != "" {49 t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)50 }5152 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 }6667 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 after76 // reading past the EOF. Open an issue if you want this change.77 assertNext(false, 6, "dir3")78}7980func TestDispenser_NextArg(t *testing.T) {81 input := `dir1 arg182 dir2 arg2 arg383 dir3`84 d := NewDispenser("Testfile", strings.NewReader(input))8586 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 }9798 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 }117118 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}126127func TestDispenser_NextLine(t *testing.T) {128 input := `host:port129 dir1 arg1130 dir2 arg2 arg3`131 d := NewDispenser("Testfile", strings.NewReader(input))132133 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 }144145 assertNextLine(true, "host:port", 0)146 assertNextLine(true, "dir1", 1)147 assertNextLine(false, "dir1", 1)148 d.Next() // arg1149 assertNextLine(true, "dir2", 3)150 assertNextLine(false, "dir2", 3)151 d.Next() // arg2152 assertNextLine(false, "arg2", 4)153 d.Next() // arg3154 assertNextLine(false, "arg3", 5)155}156157func TestDispenser_NextBlock(t *testing.T) {158 input := `foobar1 {159 sub1 arg1160 sub2161 }162 foobar2 {163 }`164 d := NewDispenser("Testfile", strings.NewReader(input))165166 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 }177178 assertNextBlock(false, -1, 0)179 d.Next() // foobar1180 assertNextBlock(true, 2, 1)181 assertNextBlock(true, 3, 1)182 assertNextBlock(true, 4, 1)183 assertNextBlock(false, 5, 0)184 d.Next() // foobar2185 assertNextBlock(false, 8, 0) // empty block is as if it didn't exist186}187188func TestDispenser_Args(t *testing.T) {189 var s1, s2, s3 string190 input := `dir1 arg1 arg2 arg3191 dir2 arg4 arg5192 dir3 arg6 arg7193 dir4`194 d := NewDispenser("Testfile", strings.NewReader(input))195196 d.Next() // dir1197198 // As many strings as arguments199 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 }211212 d.Next() // dir2213214 // More strings than arguments215 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 }227228 // (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 }232233 d.Next() // dir3234235 // More arguments than strings236 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 }242243 d.Next() // dir4244245 // No arguments or strings246 if all := d.Args(); !all {247 t.Error("Args(): Expected true, got false")248 }249250 // No arguments but at least one string251 if all := d.Args(&s1); all {252 t.Error("Args(): Expected false, got true")253 }254}255256func TestDispenser_RemainingArgs(t *testing.T) {257 input := `dir1 arg1 arg2 arg3258 dir2 arg4 arg5259 dir3 arg6 { arg7260 dir4`261 d := NewDispenser("Testfile", strings.NewReader(input))262263 d.Next() // dir1264265 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 }269270 d.Next() // dir2271272 args = d.RemainingArgs()273 if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) {274 t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)275 }276277 d.Next() // dir3278279 args = d.RemainingArgs()280 if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) {281 t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)282 }283284 d.Next() // {285 d.Next() // arg7286 d.Next() // dir4287288 args = d.RemainingArgs()289 if len(args) != 0 {290 t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args)291 }292}293294func TestDispenser_ArgErr_Err(t *testing.T) {295 input := `dir1 {296 }297 dir2 arg1 arg2`298 d := NewDispenser("Testfile", strings.NewReader(input))299300 d.cursor = 1 // {301302 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 }305306 d.cursor = 5 // arg2307308 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 }311312 err := d.Err("foobar")313 if err == nil {314 t.Fatalf("Err(): Expected an error, got nil")315 }316317 if !strings.Contains(err.Error(), "Testfile:3") {318 t.Errorf("Expected error message with filename:line in it; got '%v'", err)319 }320321 if !strings.Contains(err.Error(), "foobar") {322 t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)323 }324}