ruderich.org/simon/gswgf@v0.2.3/gswgf_test.go (about) 1 // Copyright (C) 2019-2020 Simon Ruderich 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16 package gswgf 17 18 import ( 19 "io/ioutil" 20 "log" 21 "os" 22 "path/filepath" 23 "reflect" 24 "regexp" 25 "strings" 26 "testing" 27 28 "github.com/google/go-cmp/cmp" 29 ) 30 31 type file struct { 32 path string 33 mode os.FileMode 34 data string 35 } 36 37 func TestMain(t *testing.T) { 38 var logBuilder strings.Builder 39 { 40 savedArgs := os.Args 41 os.Args = []string{"test", "build"} 42 43 savedFlags := log.Flags() 44 log.SetFlags(0) 45 log.SetOutput(&logBuilder) 46 47 cmdPath = "../cmd" 48 49 defer func() { 50 os.Args = savedArgs 51 log.SetFlags(savedFlags) 52 log.SetOutput(os.Stderr) 53 }() 54 } 55 err := os.Chdir("testdata") 56 if err != nil { 57 t.Fatal(err) 58 } 59 60 tests := []struct { 61 name string 62 cfg Config 63 logExp []string 64 log2Exp []string 65 filesExp []file 66 }{ 67 {"simple", simpleCfg, simpleLog, simpleLog2, simpleFiles}, 68 {"basic", basicCfg, basicLog, basicLog2, basicFiles}, 69 {"hooks", hooksCfg, hooksLog, hooksLog2, hooksFiles}, 70 } 71 72 for _, tc := range tests { 73 t.Run(tc.name, func(t *testing.T) { 74 tmp, err := ioutil.TempDir("", "gswgf-test") 75 if err != nil { 76 t.Fatal(err) 77 } 78 defer os.RemoveAll(tmp) 79 tc.cfg.TargetDir = tmp 80 81 for i, x := range tc.filesExp { 82 tc.filesExp[i].path = filepath.Join(tmp, x.path) 83 } 84 85 // First run 86 logBuilder.Reset() 87 err = main(tc.cfg) 88 if err != nil { 89 t.Errorf("err = %#v, want %#v", err, nil) 90 } 91 testDiff(t, tmp, &logBuilder, tc.logExp, tc.filesExp) 92 93 // Second run must not change any files but may have 94 // different log output 95 logBuilder.Reset() 96 err = main(tc.cfg) 97 if err != nil { 98 t.Errorf("err2 = %#v, want %#v", err, nil) 99 } 100 testDiff(t, tmp, &logBuilder, tc.log2Exp, tc.filesExp) 101 }) 102 } 103 } 104 func testDiff(t *testing.T, tmp string, logBuilder *strings.Builder, 105 logExp []string, filesExp []file) { 106 107 logOut := strings.Split(logBuilder.String(), "\n") 108 if !reflect.DeepEqual(logExp, logOut) { 109 t.Errorf("logs: %s", cmp.Diff(logExp, logOut)) 110 } 111 112 var files []file 113 filepath.Walk(tmp, func(path string, info os.FileInfo, err error) error { 114 if err != nil { 115 panic(err) 116 } 117 mode := info.Mode() & os.ModeType 118 f := file{ 119 path: path, 120 mode: mode, 121 } 122 if mode == 0 { 123 data, err := ioutil.ReadFile(path) 124 if err != nil { 125 panic(err) 126 } 127 f.data = string(data) 128 } else if mode == os.ModeSymlink { 129 data, err := os.Readlink(path) 130 if err != nil { 131 panic(err) 132 } 133 f.data = string(data) 134 } 135 files = append(files, f) 136 return nil 137 }) 138 if !reflect.DeepEqual(filesExp, files) { 139 cmpOpts := cmp.AllowUnexported(file{}) 140 t.Errorf("files: %s", cmp.Diff(filesExp, files, cmpOpts)) 141 } 142 } 143 144 var simpleCfg = Config{ 145 Version: 1, 146 } 147 var simpleLog = []string{ 148 `".hidden": copying`, 149 `".hidden2": copying`, 150 `".hidden3": copying`, 151 `"index.adoc": converting with Asciidoctor`, 152 `"sub/dir/.hidden": copying`, 153 `"sub/dir/test.txt": copying`, 154 `"sub/index.adoc": converting with Asciidoctor`, 155 `"test.adoc": converting with Asciidoctor`, 156 `"unknown": copying`, 157 `"unknown.ext": copying`, 158 "", 159 } 160 var simpleLog2 = []string{ 161 "", 162 } 163 var simpleFiles = []file{ 164 file{ 165 path: "", 166 mode: os.ModeDir, 167 }, 168 file{ 169 path: ".hidden", 170 data: "Hidden files are copied as well\n", 171 }, 172 file{ 173 path: ".hidden2", 174 data: "Hidden files are copied as well ... {{.Data}}\n\n{{cite \"me\"}}\n", 175 }, 176 file{ 177 path: ".hidden3", 178 data: "Hidden files are copied as well!\n", 179 }, 180 file{ 181 path: "index.html", 182 data: "Title: Index\nBody: <div class=\"paragraph\">\n<p>Hello World!</p>\n</div>\n\nMeta: html5\n", 183 }, 184 file{ 185 path: "sub", 186 mode: os.ModeDir, 187 }, 188 file{ 189 path: "sub/dir", 190 mode: os.ModeDir, 191 }, 192 file{ 193 path: "sub/dir/.hidden", 194 data: "Hidden files are copied as well, even in sub directories\n", 195 }, 196 file{ 197 path: "sub/dir/test.txt", 198 data: "= Another file\n\nThis time in a sub-directory\n", 199 }, 200 file{ 201 path: "sub/index.html", 202 data: "SUB!\nTitle: Sub-Index\nBody: <div class=\"ulist\">\n<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n</div>\n\nMeta: html5\n", 203 }, 204 file{ 205 path: "sub/test.html", 206 mode: os.ModeSymlink, 207 data: "../test.html", 208 }, 209 file{ 210 path: "test.html", 211 data: "Title: Test (test.adoc)\nBody: <div class=\"paragraph\">\n<p>Another file …​ <a href=\"https://example.org/\" class=\"bare\">https://example.org/</a></p>\n</div>\n<div class=\"paragraph\">\n<p>Test: A (test.adoc)</p>\n</div>\n<div class=\"paragraph\">\n<p>Test: B (test.adoc)</p>\n</div>\n\nMeta: html5\n", 212 }, 213 file{ 214 path: "unknown", 215 data: "Files without extension are simply copied.\n", 216 }, 217 file{ 218 path: "unknown.ext", 219 data: "Files with unknown extensions are simply copied.\n", 220 }, 221 } 222 223 var basicCfg = Config{ 224 Version: 1, 225 PathActions: append([]PathAction{ 226 PathAction{ 227 Regexp: regexp.MustCompile(`^index\.adoc$`), 228 Action: Action{ 229 Converter: ConvertCopy{}, 230 // No extension given 231 }, 232 }, 233 PathAction{ 234 Glob: "test.adoc", 235 Action: Action{ 236 NoTemplate: true, 237 Converter: ConvertAsciidoctor{}, 238 NewExt: ".htm", 239 }, 240 }, 241 PathAction{ 242 Regexp: regexp.MustCompile(`.hidden$`), 243 Action: Action{ 244 Ignore: true, 245 }, 246 }, 247 PathAction{ 248 Ext: ".txt", 249 Action: Action{ 250 Converter: ConvertAsciidoctor{}, 251 NewExt: ".html", 252 }, 253 }, 254 PathAction{ 255 Glob: "*/*.adoc", 256 Action: Action{ 257 Copy: true, 258 }, 259 }, 260 PathAction{ 261 Regexp: regexp.MustCompile(`.hidden3$`), 262 Action: Action{ 263 Converter: ConvertCopy{}, 264 NewExt: ".txt", 265 }, 266 }, 267 }, DefaultPathActions...), 268 PathLayouts: append([]PathLayout{ 269 PathLayout{ 270 Glob: "*/*/*.txt", 271 Layout: Layout{ 272 Path: "layout2.html", 273 }, 274 }, 275 }, DefaultPathLayouts...), 276 } 277 var basicLog = []string{ 278 `".hidden": ignoring`, 279 `".hidden2": copying`, 280 `".hidden3": converting with Copy`, 281 `"index.adoc": converting with Copy`, 282 `"sub/dir/.hidden": ignoring`, 283 `"sub/dir/test.txt": converting with Asciidoctor`, 284 `"sub/index.adoc": copying`, 285 `"test.adoc": converting with Asciidoctor`, 286 `"unknown": copying`, 287 `"unknown.ext": copying`, 288 "", 289 } 290 var basicLog2 = []string{ 291 `".hidden": ignoring`, 292 `"sub/dir/.hidden": ignoring`, 293 "", 294 } 295 var basicFiles = []file{ 296 file{ 297 path: "", 298 mode: os.ModeDir, 299 }, 300 file{ 301 path: ".hidden2", 302 data: "Hidden files are copied as well ... {{.Data}}\n\n{{cite \"me\"}}\n", 303 }, 304 file{ 305 path: ".hidden3.txt", 306 data: "Title: \nBody: Hidden files are copied as well!\n\nMeta: \n", 307 }, 308 file{ 309 path: "index", 310 data: "Title: \nBody: = Index\n\nHello World!\n\nMeta: \n", 311 }, 312 file{ 313 path: "sub", 314 mode: os.ModeDir, 315 }, 316 file{ 317 path: "sub/dir", 318 mode: os.ModeDir, 319 }, 320 file{ 321 path: "sub/dir/test.html", 322 data: "Path2: sub/dir/test.txt\nTitle2: Another file\nBody2: <div class=\"paragraph\">\n<p>This time in a sub-directory</p>\n</div>\n\n", 323 }, 324 file{ 325 path: "sub/index.adoc", 326 data: "= Sub-Index\n:foo: bar\n\n- a\n- b\n- c\n\n////\nComment\n////\n", 327 }, 328 file{ 329 path: "sub/test.html", 330 mode: os.ModeSymlink, 331 data: "../test.html", 332 }, 333 file{ 334 path: "test.htm", 335 data: "Title: {{.Title}}Test ({{.Path}})\nBody: <div class=\"paragraph\">\n<p>Another file …​ <a href=\"https://example.org/\" class=\"bare\">https://example.org/</a></p>\n</div>\n<div class=\"paragraph\">\n<p>{{define \"Test\"}}\nTest: {{.}} ({{(args).Path}})\n{{end}}</p>\n</div>\n<div class=\"paragraph\">\n<p>{{template \"Test\" \"A\"}}\n{{template \"Test\" \"B\"}}</p>\n</div>\n\nMeta: html5\n", 336 }, 337 file{ 338 path: "unknown", 339 data: "Files without extension are simply copied.\n", 340 }, 341 file{ 342 path: "unknown.ext", 343 data: "Files with unknown extensions are simply copied.\n", 344 }, 345 } 346 347 var hooksCfg = Config{ 348 Version: 1, 349 PathActions: append([]PathAction{ 350 PathAction{ 351 Regexp: regexp.MustCompile(`^index\.adoc$`), 352 Action: Action{ 353 Converter: ConvertCopy{}, 354 // No extension given 355 }, 356 }, 357 PathAction{ 358 Regexp: regexp.MustCompile(`.hidden$`), 359 Action: Action{ 360 Ignore: true, 361 }, 362 }, 363 PathAction{ 364 Ext: ".txt", 365 Action: Action{ 366 Converter: ConvertAsciidoctor{}, 367 NewExt: ".html", 368 }, 369 }, 370 PathAction{ 371 Regexp: regexp.MustCompile(`.hidden3$`), 372 Action: Action{ 373 Converter: ConvertCopy{}, 374 NewExt: ".txt", 375 }, 376 }, 377 }, DefaultPathActions...), 378 PathLayouts: append([]PathLayout{ 379 PathLayout{ 380 Glob: "*/*/*.txt", 381 Layout: Layout{ 382 Path: "layout2.html", 383 }, 384 }, 385 }, DefaultPathLayouts...), 386 ActionSelectionHook: func(path string, action Action) (Action, error) { 387 if path == "unknown.ext" { 388 if action.Copy != true { 389 panic("unknown.ext: invalid action") 390 } 391 action.Copy = false 392 action.Ignore = true 393 } 394 if path == ".hidden2" { 395 if action.Copy != true { 396 panic(".hidden2: invalid action") 397 } 398 action.Copy = false 399 action.Converter = ConvertAsciidoctor{} 400 } 401 return action, nil 402 }, 403 PreConvertHook: func(path string, args TemplateArgs) (TemplateArgs, error) { 404 args.Path += "!" 405 args.Body += "change" 406 if path == ".hidden2" { 407 args.Funcs["cite"] = func(x string) string { 408 return "+++<cite>+++" + x + "+++</cite>+++" 409 } 410 args.Data = "42" 411 } 412 return args, nil 413 }, 414 LayoutSelectionHook: func(path string, layout Layout) (Layout, error) { 415 if path == "sub/index.adoc" { 416 if layout.Path != "layout.html" { 417 panic("sub/index.adoc: invalid layout") 418 } 419 layout.Path = "layout2.html" 420 } 421 if layout.Path == "layout2.html" { 422 if path != "sub/index.adoc" && path != "sub/dir/test.txt" { 423 panic(path + ": invalid layout") 424 } 425 layout.Path = "layout3.html" 426 } 427 return layout, nil 428 }, 429 PreLayoutHook: func(path string, args TemplateArgs) (TemplateArgs, error) { 430 args.Body += "<!-- comment -->" 431 args.Funcs["foo"] = func(x string) string { 432 return "<FOO>" + x + "</FOO>" 433 } 434 return args, nil 435 }, 436 PostLayoutHook: func(path string, content string) (string, error) { 437 if path == "index.adoc" { 438 return "copy: " + content, nil 439 } 440 if path == "unknown" { 441 panic("unknown should not use PostLayoutHook") 442 } 443 if path == "unknown.ext" { 444 panic("unknown.ext should not use PostLayoutHook") 445 } 446 return content, nil 447 }, 448 } 449 var hooksLog = []string{ 450 `".hidden": ignoring`, 451 `".hidden2": converting with Asciidoctor`, 452 `".hidden3": converting with Copy`, 453 `"index.adoc": converting with Copy`, 454 `"sub/dir/.hidden": ignoring`, 455 `"sub/dir/test.txt": converting with Asciidoctor`, 456 `"sub/index.adoc": converting with Asciidoctor`, 457 `"test.adoc": converting with Asciidoctor`, 458 `"unknown": copying`, 459 `"unknown.ext": ignoring`, 460 "", 461 } 462 var hooksLog2 = []string{ 463 `".hidden": ignoring`, 464 `"sub/dir/.hidden": ignoring`, 465 `"unknown.ext": ignoring`, 466 "", 467 } 468 var hooksFiles = []file{ 469 file{ 470 path: "", 471 mode: os.ModeDir, 472 }, 473 file{ 474 path: ".hidden2", 475 data: "Title: \nBody: <div class=\"paragraph\">\n<p>Hidden files are copied as well …​ 42</p>\n</div>\n<div class=\"paragraph\">\n<p><cite>me</cite>\nchange</p>\n</div>\n<!-- comment -->\nMeta: html5\n", 476 }, 477 file{ 478 path: ".hidden3.txt", 479 data: "Title: \nBody: Hidden files are copied as well!\nchange<!-- comment -->\nMeta: \n", 480 }, 481 file{ 482 path: "index", 483 data: "copy: Title: \nBody: = Index\n\nHello World!\nchange<!-- comment -->\nMeta: \n", 484 }, 485 file{ 486 path: "sub", 487 mode: os.ModeDir, 488 }, 489 file{ 490 path: "sub/dir", 491 mode: os.ModeDir, 492 }, 493 file{ 494 path: "sub/dir/test.html", 495 data: "Path3: sub/dir/test.txt\nTitle3: Another file\nBody3: <div class=\"paragraph\">\n<p>This time in a sub-directory\nchange</p>\n</div>\n<!-- comment -->\n\n<FOO>test</FOO>\n", 496 }, 497 file{ 498 path: "sub/index.html", 499 data: "Path3: sub/index.adoc\nTitle3: Sub-Index\nBody3: <div class=\"ulist\">\n<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n</div>\n<div class=\"paragraph\">\n<p>change</p>\n</div>\n<!-- comment -->\n\n<FOO>test</FOO>\n", 500 }, 501 file{ 502 path: "sub/test.html", 503 mode: os.ModeSymlink, 504 data: "../test.html", 505 }, 506 file{ 507 path: "test.html", 508 data: "Title: Test (test.adoc!)\nBody: <div class=\"paragraph\">\n<p>Another file …​ <a href=\"https://example.org/\" class=\"bare\">https://example.org/</a></p>\n</div>\n<div class=\"paragraph\">\n<p>Test: A (test.adoc!)</p>\n</div>\n<div class=\"paragraph\">\n<p>Test: B (test.adoc!)</p>\n</div>\n<div class=\"paragraph\">\n<p>change</p>\n</div>\n<!-- comment -->\nMeta: html5\n", 509 }, 510 file{ 511 path: "unknown", 512 data: "Files without extension are simply copied.\n", 513 }, 514 }