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 &#8230;&#8203; <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 &#8230;&#8203; <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 &#8230;&#8203; 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 &#8230;&#8203; <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  }