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*/1819package table2021import (22 "os"23 "reflect"24 "testing"25 "time"2627 "github.com/foxcpp/maddy/framework/config"28 "github.com/foxcpp/maddy/internal/testutils"29)3031func TestReadFile(t *testing.T) {32 test := func(file string, expected map[string][]string) {33 t.Helper()3435 f, err := os.CreateTemp("", "maddy-tests-")36 if err != nil {37 t.Fatal(err)38 }39 defer os.Remove(f.Name())40 defer f.Close()41 if _, err := f.WriteString(file); err != nil {42 t.Fatal(err)43 }4445 actual := map[string][]string{}46 err = readFile(f.Name(), actual)47 if expected == nil {48 if err == nil {49 t.Errorf("expected failure, got %+v", actual)50 }51 return52 }53 if err != nil {54 t.Errorf("unexpected failure: %v", err)55 return56 }5758 if !reflect.DeepEqual(actual, expected) {59 t.Errorf("wrong results\n want %+v\n got %+v", expected, actual)60 }61 }6263 test("a: b", map[string][]string{"a": {"b"}})64 test("a@example.org: b@example.com", map[string][]string{"a@example.org": {"b@example.com"}})65 test(`"a @ a"@example.org: b@example.com`, map[string][]string{`"a @ a"@example.org`: {"b@example.com"}})66 test(`a@example.org: "b @ b"@example.com`, map[string][]string{`a@example.org`: {`"b @ b"@example.com`}})67 test(`"a @ a": "b @ b"`, map[string][]string{`"a @ a"`: {`"b @ b"`}})68 test("a: b, c", map[string][]string{"a": {"b", "c"}})69 test("a: b\na: c", map[string][]string{"a": {"b", "c"}})70 test(": b", nil)71 test(":", nil)72 test("aaa", map[string][]string{"aaa": {""}})73 test(": b", nil)74 test(" testing@example.com : arbitrary-whitespace@example.org ",75 map[string][]string{"testing@example.com": {"arbitrary-whitespace@example.org"}})76 test(`# skip comments77a: b`, map[string][]string{"a": {"b"}})78 test(`# and empty lines7980a: b`, map[string][]string{"a": {"b"}})81 test("# with whitespace too\n \na: b", map[string][]string{"a": {"b"}})82 test("a: b\na: c", map[string][]string{"a": {"b", "c"}})83}8485func TestFileReload(t *testing.T) {86 t.Parallel()8788 const file = `cat: dog`8990 f, err := os.CreateTemp("", "maddy-tests-")91 if err != nil {92 t.Fatal(err)93 }94 defer os.Remove(f.Name())95 if _, err := f.WriteString(file); err != nil {96 f.Close()97 t.Fatal(err)98 }99 f.Close()100101 mod, err := NewFile("", "", nil, []string{f.Name()})102 if err != nil {103 t.Fatal(err)104 }105 m := mod.(*File)106 m.log = testutils.Logger(t, "file_map")107 defer m.Close()108109 if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil {110 t.Fatal(err)111 }112113 // ensure it is correctly loaded at first time.114 m.mLck.RLock()115 if m.m["cat"] == nil {116 t.Fatalf("wrong content loaded, new m were not loaded, %v", m.m)117 }118 m.mLck.RUnlock()119120 for i := 0; i < 100; i++ {121 // try to provoke race condition on file writing122 if i%2 == 0 {123 if err := os.WriteFile(f.Name(), []byte("dog: cat"), os.ModePerm); err != nil {124 t.Fatal(err)125 }126 }127 time.Sleep(reloadInterval + 5*time.Millisecond)128 m.mLck.RLock()129 if m.m["dog"] == nil {130 t.Fatalf("wrong content loaded, new m were not loaded, %v", m.m)131 }132 m.mLck.RUnlock()133 }134}135136func TestFileReload_Broken(t *testing.T) {137 t.Parallel()138139 const file = `cat: dog`140141 f, err := os.CreateTemp("", "maddy-tests-")142 if err != nil {143 t.Fatal(err)144 }145 defer os.Remove(f.Name())146 if _, err := f.WriteString(file); err != nil {147 f.Close()148 t.Fatal(err)149 }150 f.Close()151152 mod, err := NewFile("", "", nil, []string{f.Name()})153 if err != nil {154 t.Fatal(err)155 }156 m := mod.(*File)157 m.log = testutils.Logger(t, FileModName)158 defer m.Close()159160 if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil {161 t.Fatal(err)162 }163164 f2, err := os.OpenFile(f.Name(), os.O_WRONLY|os.O_SYNC, os.ModePerm)165 if err != nil {166 t.Fatal(err)167 }168 if _, err := f2.WriteString(":"); err != nil {169 t.Fatal(err)170 }171 defer f2.Close()172173 time.Sleep(3 * reloadInterval)174175 m.mLck.RLock()176 defer m.mLck.RUnlock()177 if m.m["cat"] == nil {178 t.Fatal("New m were loaded or map changed", m.m)179 }180}181182func TestFileReload_Removed(t *testing.T) {183 t.Parallel()184185 const file = `cat: dog`186187 f, err := os.CreateTemp("", "maddy-tests-")188 if err != nil {189 t.Fatal(err)190 }191 if _, err := f.WriteString(file); err != nil {192 f.Close()193 t.Fatal(err)194 }195 f.Close()196197 mod, err := NewFile("", "", nil, []string{f.Name()})198 if err != nil {199 t.Fatal(err)200 }201 m := mod.(*File)202 m.log = testutils.Logger(t, FileModName)203 defer m.Close()204205 if err := mod.Init(&config.Map{Block: config.Node{}}); err != nil {206 t.Fatal(err)207 }208209 os.Remove(f.Name())210211 time.Sleep(3 * reloadInterval)212213 m.mLck.RLock()214 defer m.mLck.RUnlock()215 if m.m["cat"] != nil {216 t.Fatal("Old m are still loaded")217 }218}219220func init() {221 reloadInterval = 10 * time.Millisecond222}