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  }