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	"log"
 37	"strings"
 38	"testing"
 39)
 40
 41type lexerTestCase struct {
 42	input    string
 43	expected []Token
 44}
 45
 46func 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:123
 56
 57					directive`,
 58			expected: []Token{
 59				{Line: 1, Text: "host:123"},
 60				{Line: 3, Text: "directive"},
 61			},
 62		},
 63		{
 64			input: `host:123 {
 65						directive
 66					}`,
 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						#comment
 86						directive
 87						# comment
 88						foobar # another comment
 89					}`,
 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" b
100					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 line
130					break inside" {
131						foobar
132					}`,
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 mark
166			expected: []Token{
167				{Line: 1, Text: ":8080"},
168			},
169		},
170	}
171
172	for i, testCase := range testCases {
173		actual := tokenize(testCase.input)
174		lexerCompare(t, i, testCase.expected, actual)
175	}
176}
177
178func 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	return
187}
188
189func 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	}
193
194	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			break
199		}
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			break
204		}
205	}
206}