modernc.org/knuth@v0.0.4/mf/all_test.go (about) 1 // Copyright 2023 The Knuth Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package mf // modernc.org/knuth/mf 6 7 import ( 8 "bytes" 9 "encoding/hex" 10 "io" 11 "os" 12 "path/filepath" 13 "strings" 14 "testing" 15 gotime "time" 16 17 "github.com/pmezard/go-difflib/difflib" 18 "modernc.org/knuth/gftype" 19 "modernc.org/knuth/mf/internal/trap" 20 "modernc.org/knuth/tftopl" 21 ) 22 23 const timeout = 10 * gotime.Minute 24 25 func TestMain(m *testing.M) { 26 os.Exit(m.Run()) 27 } 28 29 func Test(t *testing.T) { 30 g, err := filepath.Glob(filepath.FromSlash("testdata/*")) 31 if err != nil { 32 t.Fatal(err) 33 } 34 35 testdata := map[string][]byte{} 36 for _, v := range g { 37 b, err := os.ReadFile(v) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 nm := filepath.Base(v) 43 if _, ok := testdata[nm]; ok { 44 t.Fatalf("internal error: %q", nm) 45 } 46 47 testdata[nm] = b 48 t.Logf("%q: %v", nm, len(b)) 49 } 50 51 wd, err := os.Getwd() 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 wd, err = filepath.Abs(wd) 57 if err != nil { 58 t.Fatal(err) 59 } 60 61 defer func() { 62 if err := os.Chdir(wd); err != nil { 63 t.Fatal(err) 64 } 65 }() 66 67 tmp := t.TempDir() 68 if err := os.WriteFile(filepath.Join(tmp, "trap.mf"), testdata["trap.mf"], 0660); err != nil { 69 t.Fatal(err) 70 } 71 72 if err := os.Chdir(tmp); err != nil { 73 t.Fatal(err) 74 } 75 76 // trapman.pdf 77 // 78 // Appendix A: How to test METAFONT. 79 80 // 0. Let’s assume that you have a tape containing TRAP.MF, TRAPIN.LOG, 81 // TRAP.LOG, TRAP.TYP, TRAP.PL, and TRAP.FOT, as in Appendices B, C, D, E, F, 82 83 /// ^ In testdata/ 84 85 // and G. Furthermore, let’s suppose that you have a working WEB system, and 86 // that you have working programs TFtoPL and GFtype, as described in the 87 // TEXware and METAFONTware reports. 88 89 /// ^ The plan is to use our own packages in modernc.org knuth/{tftopl,gftype} 90 91 // 1. Prepare a version of INIMF. (This means that your WEB change file should 92 // have init and tini defined to be null.) The debug and gubed macros should be 93 // null, in order to activate special printouts that occur when tracingedges > 94 // 1.0. The stat and tats macros should also be null, so that statistics are 95 // kept. Set mem top and mem max to 3000 (or to mem min plus 3000, if mem min 96 // isn’t zero), for purposes of this test version. Also set error line = 64, 97 // half error line = 32, max print line = 72, screen width = 100, and screen 98 // depth = 200; these parameters affect many of the lines of the test output, 99 // so your job will be much easier if you use the same settings that were used 100 // to produce Appendix E. Also (if possible) set gf buf size = 8, since this 101 // tests more parts of the program. You probably should also use the “normal” 102 // settings of other parameters found in MF.WEB (e.g., max internal = 100, buf 103 // size = 500, etc.), since these show up in a few lines of the test output. 104 // Finally, change METAFONT’s screen-display routines by putting the following 105 // simple lines in the change file: 106 // 107 // @x Screen routines: 108 // begin init_screen:=false; 109 // @y 110 // begin init_screen:=true; {screen instructions will be logged} 111 // @z 112 // 113 // None of the other screen routines (update screen , blank rectangle , paint 114 // row ) should be changed in any way; the effect will be to have METAFONT’s 115 // actions recorded in the transcript files instead of on the screen, in a 116 // machine-independent way. 117 118 /// ^ See internal/trap/mf.ch 119 120 test2(t, testdata) 121 test3(t, testdata) 122 test4(t, testdata) 123 test5(t, testdata) 124 test6(t, testdata) 125 } 126 127 // 2. Run the INIMF prepared in step 1. In response to the first ‘**’ prompt, 128 // type carriage return (thus getting another ‘**’). Then type ‘\input trap’. 129 // You should get an output that matches the file TRAPIN.LOG (Appendix C). 130 // Don’t be alarmed by the error messages that you see, unless they are 131 // different from those in Appendix C. 132 func test2(t *testing.T, testdata map[string][]byte) { 133 stdin := strings.NewReader("\\input trap\n") 134 stdoutR, stdoutW := io.Pipe() 135 stdout := bytes.NewBuffer(nil) 136 stderr := bytes.NewBuffer(nil) 137 done := make(chan error, 2) 138 139 go func() { 140 141 defer stdoutW.Close() 142 143 done <- trap.Main(stdin, stdoutW, stderr, opener) 144 }() 145 146 must(t, stdoutR, "This is METAFONT, Version 2.71828182 (TRAP) (INIMF)\n**") 147 148 go func() { 149 b := make([]byte, 1000) 150 for { 151 n, err := stdoutR.Read(b) 152 if n != 0 { 153 stdout.Write(b[:n]) 154 continue 155 } 156 157 if err == io.EOF { 158 err = nil 159 } 160 161 done <- err 162 } 163 }() 164 165 to := gotime.After(timeout) 166 for i := 0; i < 2; i++ { 167 select { 168 case err := <-done: 169 if err != nil { 170 t.Fatal(err) 171 } 172 case <-to: 173 t.Fatal("timeout") 174 } 175 } 176 177 got, err := os.ReadFile("trap.log") 178 if err != nil { 179 t.Fatal(err) 180 } 181 182 if g, e := noBanner(string(got)), noBanner(string(testdata["trapin.log"])); g != e { 183 t.Logf("stderr:\n%s", stderr.Bytes()) 184 diff := difflib.UnifiedDiff{ 185 A: difflib.SplitLines(e), 186 B: difflib.SplitLines(g), 187 FromFile: "trapin.log", 188 ToFile: "trap.log", 189 Context: 0, 190 } 191 s, _ := difflib.GetUnifiedDiffString(diff) 192 t.Fatalf( 193 "result differs\n%v\n--- expected\n%s\n\n--- got\n%s\n\n--- expected\n%s\n--- got\n%s", 194 s, e, g, hex.Dump([]byte(e)), hex.Dump([]byte(g)), 195 ) 196 } 197 198 t.Logf("%v bytes matches trapin.log OK", len(got)) 199 } 200 201 // 3. Run INIMF again. This time type ‘ &trap trap ’. (The spaces in this input 202 // help to check certain parts of METAFONT that aren’t otherwise used.) You 203 // should get outputs TRAP.LOG, TRAP.72270GF, and TRAP.TFM. Furthermore, your 204 // terminal should receive output that matches TRAP.FOT (Appendix G). During 205 // the middle part of this test, however, the terminal will not be getting 206 // output, because batchmode is being tested; don’t worry if nothing seems to 207 // be happening for a while—nothing is supposed to. 208 func test3(t *testing.T, testdata map[string][]byte) { 209 stdin := strings.NewReader(" &trap trap\n") 210 stdoutR, stdoutW := io.Pipe() 211 stdout := bytes.NewBuffer(nil) 212 stderr := bytes.NewBuffer(nil) 213 done := make(chan error, 2) 214 215 go func() { 216 217 defer stdoutW.Close() 218 219 done <- trap.Main(stdin, stdoutW, stderr, opener) 220 }() 221 222 must(t, stdoutR, "This is METAFONT, Version 2.71828182 (TRAP) (INIMF)\n**") 223 224 go func() { 225 b := make([]byte, 1000) 226 for { 227 n, err := stdoutR.Read(b) 228 if n != 0 { 229 stdout.Write(b[:n]) 230 continue 231 } 232 233 if err == io.EOF { 234 err = nil 235 } 236 237 done <- err 238 } 239 }() 240 241 to := gotime.After(timeout) 242 for i := 0; i < 2; i++ { 243 select { 244 case err := <-done: 245 if err != nil { 246 t.Fatal(err) 247 } 248 case <-to: 249 t.Fatal("timeout") 250 } 251 } 252 253 got := stdout.String() 254 exp := string(testdata["trap.fot"]) 255 exp = exp[strings.Index(exp, "(trap.mf"):] 256 257 if g, e := got, exp; g != e { 258 t.Logf("stderr:\n%s", stderr.Bytes()) 259 diff := difflib.UnifiedDiff{ 260 A: difflib.SplitLines(e), 261 B: difflib.SplitLines(g), 262 FromFile: "stdout", 263 ToFile: "trap.fot", 264 Context: 0, 265 } 266 s, _ := difflib.GetUnifiedDiffString(diff) 267 t.Fatalf( 268 "result differs\n%v\n--- expected\n%s\n\n--- got\n%s\n\n--- expected\n%s\n--- got\n%s", 269 s, e, g, hex.Dump([]byte(e)), hex.Dump([]byte(g)), 270 ) 271 } 272 273 t.Logf("%v bytes matches trap.fot OK", len(got)) 274 } 275 276 func noBanner(s string) string { 277 x := strings.Index(s, "This is ") 278 x2 := strings.Index(s[x:], "\n") 279 return s[:x] + s[x2+1:] 280 } 281 282 func must(t *testing.T, r io.Reader, expect string) { 283 b := make([]byte, len(expect)) 284 if _, err := io.ReadFull(r, b); err != nil || string(b) != expect { 285 t.Fatalf("got %q, expected %q, err %v", b, expect, err) 286 } 287 } 288 289 // 4. Compare the TRAP.LOG file from step 3 with the “master” TRAP.LOG file of 290 // step 0. (Let’s hope you put that master file in a safe place so that it 291 // wouldn’t be clobbered.) There should be perfect agreement between these 292 // files except in the following respects: 293 // 294 // a) The dates and possibly the file names will naturally be different. 295 // 296 // b) If you had different values for stack size , buf size , etc., the 297 // corresponding capacity values will be different when they are printed out at 298 // the end. 299 // 300 // c) Help messages may be different; indeed, the author encourages non-English 301 // help messages in versions of METAFONT for people who don’t understand 302 // English as well as some other language. 303 // 304 // d) The total number and length of strings at the end and/or “still 305 // untouched” may well be different. 306 // 307 // e) If your METAFONT uses a different memory allocation or packing scheme, 308 // the memory usage statis- tics may change. 309 // 310 // f) If you use a different storage allocation scheme, the capsule numbers 311 // will probably be different, but the order of variables should be unchanged 312 // when dependent variables are shown. METAFONT should also choose the same 313 // variables to be dependent. 314 // 315 // g) If your computer handles integer division of negative operands in a 316 // nonstandard way, you may get results that are rounded differently. Although 317 // TEX is careful to be machine-independent in this regard, METAFONT is not, 318 // because integer divisions are present in so many places. 319 func test4(t *testing.T, testdata map[string][]byte) { 320 b, err := os.ReadFile("trap.log") 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 g, e := noBanner(string(b)), noBanner(string(testdata["trap.log"])) 326 if g != e { 327 diff := difflib.UnifiedDiff{ 328 A: difflib.SplitLines(e), 329 B: difflib.SplitLines(g), 330 FromFile: "trap.log.0", 331 ToFile: "trap.log.3", 332 Context: 0, 333 } 334 s, _ := difflib.GetUnifiedDiffString(diff) 335 t.Fatalf( 336 "result differs\n%v\n--- expected\n%s\n\n--- got\n%s\n\n--- expected\n%s\n--- got\n%s", 337 s, e, g, hex.Dump([]byte(e)), hex.Dump([]byte(g)), 338 ) 339 } 340 341 t.Logf("%v bytes matches trap.log.0 OK", len(g)) 342 } 343 344 // 5. Use GFtype to convert your file TRAP.72270GF to a file TRAP.TYP. (Both of 345 // GFtype’s options, i.e., mnemonic output and image output, should be enabled 346 // so that you get the maximum amount of output.) The resulting file should 347 // agree with the master TRAP.TYP file of step 0, assuming that your GFtype has 348 // the “normal” values of compile-time constants (top pixel = 69, etc.). 349 func test5(t *testing.T, testdata map[string][]byte) { 350 gfFile, err := os.Open("trap.72270gf") 351 if err != nil { 352 t.Fatal(err) 353 } 354 355 defer gfFile.Close() 356 357 stdout := bytes.NewBuffer(nil) 358 stderr := bytes.NewBuffer(nil) 359 if err := gftype.Main(gfFile, stdout, stderr, true, true); err != nil { 360 t.Fatal(err) 361 } 362 363 g, e := noBanner(stdout.String()), noBanner(string(testdata["trap.typ"])) 364 if g != e { 365 diff := difflib.UnifiedDiff{ 366 A: difflib.SplitLines(e), 367 B: difflib.SplitLines(g), 368 FromFile: "stdout", 369 ToFile: "trap.typ", 370 Context: 0, 371 } 372 s, _ := difflib.GetUnifiedDiffString(diff) 373 t.Fatalf( 374 "result differs\n%v\n--- expected\n%s\n\n--- got\n%s\n\n--- expected\n%s\n--- got\n%s", 375 s, e, g, hex.Dump([]byte(e)), hex.Dump([]byte(g)), 376 ) 377 } 378 379 t.Logf("%v bytes matches trap.typ OK", len(g)) 380 } 381 382 // 6. Use TFtoPL to convert your file TRAP.TFM to a file TRAP.PL. The resulting 383 // file should agree with the master TRAP.PL file of step 0. 384 func test6(t *testing.T, testdata map[string][]byte) { 385 tfmFile, err := os.Open("trap.tfm") 386 if err != nil { 387 t.Fatal(err) 388 } 389 390 defer tfmFile.Close() 391 392 plFile := bytes.NewBuffer(nil) 393 stdout := bytes.NewBuffer(nil) 394 stderr := bytes.NewBuffer(nil) 395 if err := tftopl.Main(tfmFile, plFile, stdout, stderr); err != nil { 396 t.Fatal(err) 397 } 398 399 g, e := plFile.String(), string(testdata["trap.pl"]) 400 if g != e { 401 diff := difflib.UnifiedDiff{ 402 A: difflib.SplitLines(e), 403 B: difflib.SplitLines(g), 404 FromFile: "plFile", 405 ToFile: "trap.pl", 406 Context: 0, 407 } 408 s, _ := difflib.GetUnifiedDiffString(diff) 409 t.Fatalf( 410 "result differs\n%v\n--- expected\n%s\n\n--- got\n%s\n\n--- expected\n%s\n--- got\n%s", 411 s, e, g, hex.Dump([]byte(e)), hex.Dump([]byte(g)), 412 ) 413 } 414 415 t.Logf("%v bytes matches trap.pl OK", len(g)) 416 }