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 "log"37 "strings"38 "testing"39)4041type lexerTestCase struct {42 input string43 expected []Token44}4546func TestLexer(t *testing.T) {47 testCases := []lexerTestCase{48 {49 input: `host:123`,50 expected: []Token{51 {Line: 1, Text: "host:123"},52 },53 },54 {55 input: `host:1235657 directive`,58 expected: []Token{59 {Line: 1, Text: "host:123"},60 {Line: 3, Text: "directive"},61 },62 },63 {64 input: `host:123 {65 directive66 }`,67 expected: []Token{68 {Line: 1, Text: "host:123"},69 {Line: 1, Text: "{"},70 {Line: 2, Text: "directive"},71 {Line: 3, Text: "}"},72 },73 },74 {75 input: `host:123 { directive }`,76 expected: []Token{77 {Line: 1, Text: "host:123"},78 {Line: 1, Text: "{"},79 {Line: 1, Text: "directive"},80 {Line: 1, Text: "}"},81 },82 },83 {84 input: `host:123 {85 #comment86 directive87 # comment88 foobar # another comment89 }`,90 expected: []Token{91 {Line: 1, Text: "host:123"},92 {Line: 1, Text: "{"},93 {Line: 3, Text: "directive"},94 {Line: 5, Text: "foobar"},95 {Line: 6, Text: "}"},96 },97 },98 {99 input: `a "quoted value" b100 foobar`,101 expected: []Token{102 {Line: 1, Text: "a"},103 {Line: 1, Text: "quoted value"},104 {Line: 1, Text: "b"},105 {Line: 2, Text: "foobar"},106 },107 },108 {109 input: `A "quoted \"value\" inside" B`,110 expected: []Token{111 {Line: 1, Text: "A"},112 {Line: 1, Text: `quoted "value" inside`},113 {Line: 1, Text: "B"},114 },115 },116 {117 input: `"don't\escape"`,118 expected: []Token{119 {Line: 1, Text: `don't\escape`},120 },121 },122 {123 input: `"don't\\escape"`,124 expected: []Token{125 {Line: 1, Text: `don't\\escape`},126 },127 },128 {129 input: `A "quoted value with line130 break inside" {131 foobar132 }`,133 expected: []Token{134 {Line: 1, Text: "A"},135 {Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},136 {Line: 2, Text: "{"},137 {Line: 3, Text: "foobar"},138 {Line: 4, Text: "}"},139 },140 },141 {142 input: `"C:\php\php-cgi.exe"`,143 expected: []Token{144 {Line: 1, Text: `C:\php\php-cgi.exe`},145 },146 },147 {148 input: `empty "" string`,149 expected: []Token{150 {Line: 1, Text: `empty`},151 {Line: 1, Text: ``},152 {Line: 1, Text: `string`},153 },154 },155 {156 input: "skip those\r\nCR characters",157 expected: []Token{158 {Line: 1, Text: "skip"},159 {Line: 1, Text: "those"},160 {Line: 2, Text: "CR"},161 {Line: 2, Text: "characters"},162 },163 },164 {165 input: "\xEF\xBB\xBF:8080", // test with leading byte order mark166 expected: []Token{167 {Line: 1, Text: ":8080"},168 },169 },170 }171172 for i, testCase := range testCases {173 actual := tokenize(testCase.input)174 lexerCompare(t, i, testCase.expected, actual)175 }176}177178func tokenize(input string) (tokens []Token) {179 l := lexer{}180 if err := l.load(strings.NewReader(input)); err != nil {181 log.Printf("[ERROR] load failed: %v", err)182 }183 for l.next() {184 tokens = append(tokens, l.token)185 }186 return187}188189func lexerCompare(t *testing.T, n int, expected, actual []Token) {190 if len(expected) != len(actual) {191 t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))192 }193194 for i := 0; i < len(actual) && i < len(expected); i++ {195 if actual[i].Line != expected[i].Line {196 t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",197 n, i, expected[i].Text, expected[i].Line, actual[i].Line)198 break199 }200 if actual[i].Text != expected[i].Text {201 t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",202 n, i, expected[i].Text, actual[i].Text)203 break204 }205 }206}