github.com/dpifke/golang-fastmatch@v0.0.0-20170222002913-6bd661ebeb8c/generate_test.go (about)

     1  // Copyright (c) 2014-2016 Dave Pifke.
     2  //
     3  // Redistribution and use in source and binary forms, with or without
     4  // modification, is permitted provided that the following conditions are met:
     5  //
     6  // 1. Redistributions of source code must retain the above copyright notice,
     7  //    this list of conditions and the following disclaimer.
     8  //
     9  // 2. Redistributions in binary form must reproduce the above copyright notice,
    10  //    this list of conditions and the following disclaimer in the documentation
    11  //    and/or other materials provided with the distribution.
    12  //
    13  // 3. Neither the name of the copyright holder nor the names of its
    14  //    contributors may be used to endorse or promote products derived from
    15  //    this software without specific prior written permission.
    16  //
    17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    18  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    19  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    20  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    21  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    22  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    23  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    24  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    25  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    26  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    27  // POSSIBILITY OF SUCH DAMAGE.
    28  
    29  package fastmatch
    30  
    31  import (
    32  	"fmt"
    33  	"io"
    34  	"io/ioutil"
    35  	"os"
    36  	"os/exec"
    37  	"strings"
    38  	"testing"
    39  )
    40  
    41  // testDirection is passed to generateRunnable to specify whether we should
    42  // use Generate or GenerateReverse for a particular test.
    43  type testDirection bool
    44  
    45  const (
    46  	match        testDirection = true  // use Generate
    47  	reverseMatch testDirection = false // use GenerateReverse
    48  )
    49  
    50  // generateRunnable creates a temporary directory, adds it GOPATH, and uses
    51  // Generate or GenerateReverse to create runnable string matcher code therein.
    52  //
    53  // GenerateTest is also run, to generate and run the automated self-test;
    54  // failures are reported, however are non-fatal.
    55  //
    56  // A cleanup function is returned, which should be executed by the caller at
    57  // the completion of the test.  This removes the temporary directory and
    58  // restores GOPATH and the current working directory.
    59  func generateRunnable(t *testing.T, which testDirection, retType string, cases map[string]string, none string, flags ...*Flag) (func(), error) {
    60  	cleanup := func() {}
    61  
    62  	var out, testOut io.Writer
    63  	dir, err := ioutil.TempDir("", "fastmatch_test")
    64  	if err == nil {
    65  		savedWd, _ := os.Getwd()
    66  		err = os.Chdir(dir)
    67  		if err == nil {
    68  			savedGopath := os.Getenv("GOPATH")
    69  			os.Setenv("GOPATH", fmt.Sprintf("%s:%s", dir, savedGopath))
    70  			cleanup = func() {
    71  				os.Setenv("GOPATH", savedGopath)
    72  				os.Chdir(savedWd)
    73  				os.RemoveAll(dir)
    74  			}
    75  			out, err = os.Create("generated.go")
    76  			if err == nil {
    77  				testOut, err = os.Create("generated_test.go")
    78  			}
    79  		}
    80  	}
    81  	if err != nil {
    82  		return cleanup, err
    83  	}
    84  
    85  	_, err = fmt.Fprintln(out, "package main")
    86  	if err != nil {
    87  		return cleanup, err
    88  	}
    89  	fmt.Fprintln(out)
    90  	fmt.Fprintln(out, "import (")
    91  	fmt.Fprintln(out, "\t\"fmt\"")
    92  	fmt.Fprintln(out, "\t\"os\"")
    93  	fmt.Fprintln(out, ")")
    94  	fmt.Fprintln(out)
    95  
    96  	fmt.Fprintln(out, "func match(input string)", retType, "{")
    97  	if which == match {
    98  		err = Generate(out, cases, none, flags...)
    99  	} else {
   100  		err = GenerateReverse(out, cases, none, flags...)
   101  	}
   102  	if err != nil {
   103  		return cleanup, err
   104  	}
   105  	fmt.Fprintln(out)
   106  
   107  	fmt.Fprintln(out, "func main() {")
   108  	fmt.Fprintln(out, "\tfmt.Println(match(os.Args[1]))")
   109  	_, err = fmt.Fprintln(out, "}")
   110  
   111  	// Also generate and run the self-test.  Errors generating or running
   112  	// the automated tests are recorded, but are not fatal.
   113  	var fwd, rev string
   114  	if which == match {
   115  		fwd = "match(%q)"
   116  		rev = ""
   117  	} else {
   118  		fwd = ""
   119  		rev = "match(%s)"
   120  	}
   121  	_, testErr := fmt.Fprintln(testOut, "package main")
   122  	if testErr == nil {
   123  		fmt.Fprintln(testOut)
   124  		fmt.Fprintln(testOut, "import \"testing\"")
   125  		fmt.Fprintln(testOut)
   126  		fmt.Fprintln(testOut, "func TestMatch(t *testing.T) {")
   127  		testErr = GenerateTest(testOut, fwd, rev, cases, flags...)
   128  	}
   129  	if testErr == nil {
   130  		testResult, err := exec.Command("go", "test").CombinedOutput()
   131  		if err != nil {
   132  			t.Errorf("%s: %s", err.Error(), strings.TrimSpace(string(testResult)))
   133  		}
   134  	} else {
   135  		t.Errorf("failed to generate test: %s", testErr.Error())
   136  	}
   137  
   138  	return cleanup, err
   139  }
   140  
   141  // expectMatch uses `go run` to execute our generated test.go file.  It passes
   142  // the provided input and compares the output to what the test expects.
   143  func expectMatch(t *testing.T, input, expect string) {
   144  	cmd := exec.Command("go", "run", "generated.go", input)
   145  	out, err := cmd.CombinedOutput()
   146  	if err != nil {
   147  		t.Fatalf("%s: %s", err.Error(), strings.TrimSpace(string(out)))
   148  	}
   149  
   150  	outs := strings.TrimSpace(string(out))
   151  	if outs != expect {
   152  		t.Errorf("expected %q, got %q for input %q", expect, outs, input)
   153  	}
   154  }
   155  
   156  // TestNoFlags tests a simple matcher.
   157  func TestNoFlags(t *testing.T) {
   158  	if testing.Short() {
   159  		t.Skip("skipping compiled tests in short mode")
   160  	}
   161  
   162  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   163  		"foo": "1",
   164  		"bar": "2",
   165  		"baz": "3",
   166  	}, "0")
   167  	defer cleanup()
   168  	if err != nil {
   169  		t.Fatalf(err.Error())
   170  	}
   171  
   172  	expectMatch(t, "foo", "1")
   173  	expectMatch(t, "bar", "2")
   174  	expectMatch(t, "baz", "3")
   175  	expectMatch(t, "bat", "0")
   176  	expectMatch(t, "bazz", "0")
   177  }
   178  
   179  // TestNoState tests matching a single string, no state machine required.
   180  func TestNoState(t *testing.T) {
   181  	if testing.Short() {
   182  		t.Skip("skipping compiled tests in short mode")
   183  	}
   184  
   185  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   186  		"foo": "1",
   187  	}, "0")
   188  	defer cleanup()
   189  	if err != nil {
   190  		t.Fatalf(err.Error())
   191  	}
   192  
   193  	expectMatch(t, "foo", "1")
   194  }
   195  
   196  // TestInsensitive tests a case-insensitive matcher.
   197  func TestInsensitive(t *testing.T) {
   198  	if testing.Short() {
   199  		t.Skip("skipping compiled tests in short mode")
   200  	}
   201  
   202  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   203  		"foo": "1",
   204  		"Bar": "2",
   205  		"baz": "3",
   206  	}, "0", Insensitive)
   207  	defer cleanup()
   208  	if err != nil {
   209  		t.Fatalf(err.Error())
   210  	}
   211  
   212  	expectMatch(t, "Foo", "1")
   213  	expectMatch(t, "BAR", "2")
   214  	expectMatch(t, "baz", "3")
   215  	expectMatch(t, "bat", "0")
   216  }
   217  
   218  // TestEquivalent tests a matcher which makes use of the Equivalent flag.
   219  func TestEquivalent(t *testing.T) {
   220  	if testing.Short() {
   221  		t.Skip("skipping compiled tests in short mode")
   222  	}
   223  
   224  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   225  		"foo00000": "1",
   226  		"bar11111": "2",
   227  	}, "0", Equivalent('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
   228  	defer cleanup()
   229  	if err != nil {
   230  		t.Fatalf(err.Error())
   231  	}
   232  
   233  	expectMatch(t, "foo90210", "1")
   234  	expectMatch(t, "foo11111", "1")
   235  	expectMatch(t, "bar00000", "2")
   236  	expectMatch(t, "bar12345", "2")
   237  	expectMatch(t, "fooabcde", "0")
   238  	expectMatch(t, "barzyxwv", "0")
   239  }
   240  
   241  // TestHasPrefix tests a prefix matcher.
   242  func TestHasPrefix(t *testing.T) {
   243  	if testing.Short() {
   244  		t.Skip("skipping compiled tests in short mode")
   245  	}
   246  
   247  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   248  		"f":   "1",
   249  		"Bar": "2",
   250  		"baz": "3",
   251  	}, "0", HasPrefix, Insensitive)
   252  	defer cleanup()
   253  	if err != nil {
   254  		t.Fatalf(err.Error())
   255  	}
   256  
   257  	expectMatch(t, "f", "1")
   258  	expectMatch(t, "foo", "1")
   259  	expectMatch(t, "FOO", "1")
   260  	expectMatch(t, "bar", "2")
   261  	expectMatch(t, "bart", "2")
   262  	expectMatch(t, "bz", "0")
   263  	expectMatch(t, "bzz", "0")
   264  }
   265  
   266  // TestHasSuffix tests a suffix matcher.
   267  func TestHasSuffix(t *testing.T) {
   268  	if testing.Short() {
   269  		t.Skip("skipping compiled tests in short mode")
   270  	}
   271  
   272  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   273  		"o":  "1",
   274  		"ar": "2",
   275  	}, "0", HasSuffix, Insensitive)
   276  	defer cleanup()
   277  	if err != nil {
   278  		t.Fatalf(err.Error())
   279  	}
   280  
   281  	expectMatch(t, "o", "1")
   282  	expectMatch(t, "flo", "1")
   283  	expectMatch(t, "FLO", "1")
   284  	expectMatch(t, "bao", "1")
   285  	expectMatch(t, "bar", "2")
   286  	expectMatch(t, "baz", "0")
   287  }
   288  
   289  // TestStopUpon tests a matcher that's been directed to stop when a certain
   290  // rune is encountered.
   291  func TestStopUpon(t *testing.T) {
   292  	if testing.Short() {
   293  		t.Skip("skipping compiled tests in short mode")
   294  	}
   295  
   296  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   297  		"foo": "1",
   298  		"bar": "2",
   299  	}, "0", StopUpon('.'))
   300  	defer cleanup()
   301  	if err != nil {
   302  		t.Fatalf(err.Error())
   303  	}
   304  
   305  	expectMatch(t, "foo", "1")
   306  	expectMatch(t, "foo.", "1")
   307  	expectMatch(t, "foofoo", "0")
   308  	expectMatch(t, "bar.xyz", "2")
   309  	expectMatch(t, "baz", "0")
   310  	expectMatch(t, "b.az", "0")
   311  }
   312  
   313  // TestMultipleStopUpon tests that multiple StopUpon runes can be specified
   314  // and all will be honored.
   315  func TestMultipleStopUpon(t *testing.T) {
   316  	if testing.Short() {
   317  		t.Skip("skipping compiled tests in short mode")
   318  	}
   319  
   320  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   321  		"foo?bar": "1",
   322  		"bar!foo": "2",
   323  	}, "0", StopUpon('.', '!'), StopUpon('?'))
   324  	defer cleanup()
   325  	if err != nil {
   326  		t.Fatalf(err.Error())
   327  	}
   328  
   329  	expectMatch(t, "foo", "1")
   330  	expectMatch(t, "foo.quix", "1")
   331  	expectMatch(t, "bar?!?", "2")
   332  }
   333  
   334  // TestPrefixStopUpon tests combining StopUpon and HasPrefix flags.  This is
   335  // basically the same as HasPrefix, except inputs are truncated if the stop
   336  // rune is encountered.
   337  func TestPrefixStopUpon(t *testing.T) {
   338  	if testing.Short() {
   339  		t.Skip("skipping compiled tests in short mode")
   340  	}
   341  
   342  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   343  		"foo":  "1",
   344  		"b.ar": "2",
   345  	}, "0", HasPrefix, StopUpon('.'))
   346  	defer cleanup()
   347  	if err != nil {
   348  		t.Fatalf(err.Error())
   349  	}
   350  
   351  	expectMatch(t, "foo", "1")
   352  	expectMatch(t, "foo.", "1")
   353  	expectMatch(t, "foofoo", "1")
   354  	expectMatch(t, "baz", "2")
   355  	expectMatch(t, "b.az", "2")
   356  	expectMatch(t, "quix", "0")
   357  	expectMatch(t, "q.uix", "0")
   358  }
   359  
   360  // TestSuffixStopUpon tests combining StopUpon and HasSuffix flags.
   361  func TestSuffixStopUpon(t *testing.T) {
   362  	if testing.Short() {
   363  		t.Skip("skipping compiled tests in short mode")
   364  	}
   365  
   366  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   367  		".foo": "1",
   368  		"bar":  "2",
   369  	}, "0", HasSuffix, StopUpon('.'))
   370  	defer cleanup()
   371  	if err != nil {
   372  		t.Fatalf(err.Error())
   373  	}
   374  
   375  	expectMatch(t, "foo", "1")
   376  	expectMatch(t, ".foo", "1")
   377  	expectMatch(t, "foofoo", "1")
   378  	expectMatch(t, "foo.bar", "2")
   379  	expectMatch(t, "bar", "2")
   380  	expectMatch(t, "barfar", "0")
   381  	expectMatch(t, ".", "0")
   382  }
   383  
   384  // TestStopUponEquivalent tests combining StopUpon and Equivalent flags.
   385  func TestStopUponEquivalent(t *testing.T) {
   386  	if testing.Short() {
   387  		t.Skip("skipping compiled tests in short mode")
   388  	}
   389  
   390  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   391  		"foo!bar": "1",
   392  		"bar.foo": "2",
   393  	}, "0", StopUpon('.'), Equivalent('.', '!'))
   394  	defer cleanup()
   395  	if err != nil {
   396  		t.Fatalf(err.Error())
   397  	}
   398  
   399  	expectMatch(t, "foo", "1")
   400  	expectMatch(t, "foo.", "1")
   401  	expectMatch(t, "foo!lala", "1")
   402  	expectMatch(t, "bar", "2")
   403  	expectMatch(t, "bar.", "2")
   404  	expectMatch(t, "bar!lala", "2")
   405  	expectMatch(t, "baz", "0")
   406  }
   407  
   408  // TestIgnore tests matching with ignored runes.
   409  func TestIgnore(t *testing.T) {
   410  	if testing.Short() {
   411  		t.Skip("skipping compiled tests in short mode")
   412  	}
   413  
   414  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   415  		".f.o.o.": "1",
   416  		"bar":     "2",
   417  	}, "0", Ignore('.'))
   418  	defer cleanup()
   419  	if err != nil {
   420  		t.Fatalf(err.Error())
   421  	}
   422  
   423  	expectMatch(t, "foo", "1")
   424  	expectMatch(t, "f....o...o", "1")
   425  	expectMatch(t, "...foo...", "1")
   426  	expectMatch(t, ".bar", "2")
   427  	expectMatch(t, "bar.", "2")
   428  	expectMatch(t, "bar.f", "0")
   429  	expectMatch(t, "...", "0")
   430  }
   431  
   432  // TestMultipleIgnore tests that multiple Ignore runes can be specified.
   433  func TestMultipleIgnore(t *testing.T) {
   434  	if testing.Short() {
   435  		t.Skip("skipping compiled tests in short mode")
   436  	}
   437  
   438  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   439  		"foo?bar": "1",
   440  		"bar!foo": "2",
   441  	}, "0", Ignore('.', '!'), Ignore('?'))
   442  	defer cleanup()
   443  	if err != nil {
   444  		t.Fatalf(err.Error())
   445  	}
   446  
   447  	expectMatch(t, "foo...bar", "1")
   448  	expectMatch(t, "bar?!foo", "2")
   449  }
   450  
   451  // TestPrefixIgnore tests combining Ignore and HasPrefix.
   452  func TestPrefixIgnore(t *testing.T) {
   453  	if testing.Short() {
   454  		t.Skip("skipping compiled tests in short mode")
   455  	}
   456  
   457  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   458  		".f.o.o.": "1",
   459  		"bar":     "2",
   460  	}, "0", Ignore('.'), HasPrefix)
   461  	defer cleanup()
   462  	if err != nil {
   463  		t.Fatalf(err.Error())
   464  	}
   465  
   466  	expectMatch(t, "foobar", "1")
   467  	expectMatch(t, "f....o...o....b....a.....r", "1")
   468  	expectMatch(t, "...bar", "2")
   469  	expectMatch(t, "f.a.r.", "0")
   470  }
   471  
   472  // TestSuffixIgnore tests combining Ignore and HasSuffix.
   473  func TestSuffixIgnore(t *testing.T) {
   474  	if testing.Short() {
   475  		t.Skip("skipping compiled tests in short mode")
   476  	}
   477  
   478  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   479  		".foo": "1",
   480  		"bar":  "2",
   481  	}, "0", Ignore('.'), HasSuffix)
   482  	defer cleanup()
   483  	if err != nil {
   484  		t.Fatalf(err.Error())
   485  	}
   486  
   487  	expectMatch(t, "barfoo", "1")
   488  	expectMatch(t, "bar.foo.", "1")
   489  	expectMatch(t, "z.z.z.b.a.r", "2")
   490  	expectMatch(t, "f.a.r.", "0")
   491  }
   492  
   493  // TestIgnoreEquivalent tests combining Ignore and Equivalent flags.
   494  func TestIgnoreEquivalent(t *testing.T) {
   495  	if testing.Short() {
   496  		t.Skip("skipping compiled tests in short mode")
   497  	}
   498  
   499  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   500  		"foo-bar": "1",
   501  		"bar_foo": "2",
   502  	}, "0", Ignore('-'), Equivalent('-', '_'))
   503  	defer cleanup()
   504  	if err != nil {
   505  		t.Fatalf(err.Error())
   506  	}
   507  
   508  	expectMatch(t, "foobar", "1")
   509  	expectMatch(t, "f-o-ob_a_r", "1")
   510  	expectMatch(t, "barfoo", "2")
   511  	expectMatch(t, "___barfoo---", "2")
   512  	expectMatch(t, "bar", "0")
   513  }
   514  
   515  // TestIgnoreExcept tests matching where all but a subset of runes are
   516  // ignored.
   517  func TestIgnoreExcept(t *testing.T) {
   518  	if testing.Short() {
   519  		t.Skip("skipping compiled tests in short mode")
   520  	}
   521  
   522  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   523  		"1x0x1x0x1": "1",
   524  		"zz00110zz": "2",
   525  	}, "0", IgnoreExcept('0', '1', '2'))
   526  	defer cleanup()
   527  	if err != nil {
   528  		t.Fatalf(err.Error())
   529  	}
   530  
   531  	expectMatch(t, "10101", "1")
   532  	expectMatch(t, "foo10101", "1")
   533  	expectMatch(t, "10101foo", "1")
   534  	expectMatch(t, "00-11-0", "2")
   535  	expectMatch(t, "abcdef", "0")
   536  	expectMatch(t, "101011", "0")
   537  }
   538  
   539  // TestMultipleIgnoreExcept tests that multiple IgnoreExcept flags get
   540  // combined properly.
   541  func TestMultipleIgnoreExcept(t *testing.T) {
   542  	if testing.Short() {
   543  		t.Skip("skipping compiled tests in short mode")
   544  	}
   545  
   546  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   547  		"1x0x1x0x2": "1",
   548  		"zz22110zz": "2",
   549  	}, "0", IgnoreExcept('0', '1'), IgnoreExcept('2'))
   550  	defer cleanup()
   551  	if err != nil {
   552  		t.Fatalf(err.Error())
   553  	}
   554  
   555  	expectMatch(t, "foo10102", "1")
   556  	expectMatch(t, "22110bar", "2")
   557  }
   558  
   559  // TestIgnoreExceptEquivalent tests that Equivalent applies transitively to
   560  // IgnoreExcept.
   561  func TestIgnoreExceptEquvialent(t *testing.T) {
   562  	if testing.Short() {
   563  		t.Skip("skipping compiled tests in short mode")
   564  	}
   565  
   566  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   567  		"0202": "1",
   568  		"1111": "2",
   569  	}, "0", IgnoreExcept('0', '3'), Equivalent('0', '1'), Equivalent('2', '3'))
   570  	defer cleanup()
   571  	if err != nil {
   572  		t.Fatalf(err.Error())
   573  	}
   574  
   575  	expectMatch(t, "1313", "1")
   576  	expectMatch(t, "0000", "2")
   577  }
   578  
   579  // TestIgnoreExceptStopUpon tests combining IgnoreExcept and StopUpon.
   580  func TestIgnoreExceptStopUpon(t *testing.T) {
   581  	if testing.Short() {
   582  		t.Skip("skipping compiled tests in short mode")
   583  	}
   584  
   585  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   586  		"abc123.321": "1",
   587  		"111def":     "2",
   588  	}, "0", IgnoreExcept('1', '2', '3'), StopUpon('.'))
   589  	defer cleanup()
   590  	if err != nil {
   591  		t.Fatalf(err.Error())
   592  	}
   593  
   594  	expectMatch(t, "123", "1")
   595  	expectMatch(t, "xxx111.bar", "2")
   596  	expectMatch(t, "123321", "0")
   597  	expectMatch(t, "1111", "0")
   598  }
   599  
   600  // TestPrefixIgnoreExcept tests combining IgnoreExcept and HasPrefix.
   601  func TestPrefixIgnoreExcept(t *testing.T) {
   602  	if testing.Short() {
   603  		t.Skip("skipping compiled tests in short mode")
   604  	}
   605  
   606  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   607  		"123": "1",
   608  		"111": "2",
   609  	}, "0", IgnoreExcept('1', '2', '3'), HasPrefix)
   610  	defer cleanup()
   611  	if err != nil {
   612  		t.Fatalf(err.Error())
   613  	}
   614  
   615  	expectMatch(t, "123321", "1")
   616  	expectMatch(t, "foo111", "2")
   617  	expectMatch(t, "222", "0")
   618  }
   619  
   620  // TestSuffixIgnoreExcept tests combining IgnoreExcept and HasSuffix.
   621  func TestSuffixIgnoreExcept(t *testing.T) {
   622  	if testing.Short() {
   623  		t.Skip("skipping compiled tests in short mode")
   624  	}
   625  
   626  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   627  		"123": "1",
   628  		"111": "2",
   629  	}, "0", IgnoreExcept('1', '2', '3'), HasSuffix)
   630  	defer cleanup()
   631  	if err != nil {
   632  		t.Fatalf(err.Error())
   633  	}
   634  
   635  	expectMatch(t, "321123", "1")
   636  	expectMatch(t, "foo111bar", "2")
   637  	expectMatch(t, "222", "0")
   638  }
   639  
   640  // TestChained tests chaining multiple state machines together, to match
   641  // longer strings.
   642  func TestChained(t *testing.T) {
   643  	if testing.Short() {
   644  		t.Skip("skipping compiled tests in short mode")
   645  	}
   646  
   647  	oldMaxState := maxState
   648  	defer func() { maxState = oldMaxState }()
   649  	maxState = 16
   650  
   651  	cleanup, err := generateRunnable(t, match, "int", map[string]string{
   652  		"abcdef": "1",
   653  		"ghijkl": "2",
   654  	}, "0")
   655  	defer cleanup()
   656  	if err != nil {
   657  		t.Fatalf(err.Error())
   658  	}
   659  
   660  	expectMatch(t, "abcdef", "1")
   661  	expectMatch(t, "ghijkl", "2")
   662  	expectMatch(t, "123456", "0")
   663  }
   664  
   665  // TestReverse tests a simple reverse matcher.
   666  func TestReverse(t *testing.T) {
   667  	if testing.Short() {
   668  		t.Skip("skipping compiled tests in short mode")
   669  	}
   670  
   671  	cleanup, err := generateRunnable(t, reverseMatch, "string", map[string]string{
   672  		"foo": `"1"`,
   673  		"bar": `"2"`,
   674  	}, `"baz"`)
   675  	defer cleanup()
   676  	if err != nil {
   677  		t.Fatalf(err.Error())
   678  	}
   679  
   680  	expectMatch(t, "1", "foo")
   681  	expectMatch(t, "2", "bar")
   682  	expectMatch(t, "0", "baz")
   683  }
   684  
   685  // TestBadWriter tests that Generate and GenerateReverse return an error
   686  // if passed an unusable io.Writer.
   687  func TestBadWriter(t *testing.T) {
   688  	f, _ := ioutil.TempFile("", "fastmatch_test")
   689  	f.Close()
   690  	os.Remove(f.Name())
   691  
   692  	if err := Generate(f, map[string]string{"a": "1"}, "0"); err == nil {
   693  		t.Errorf("no error from Generate on closed io.Writer")
   694  	}
   695  	if err := Generate(f, map[string]string{"a": "1"}, "0", HasPrefix); err == nil {
   696  		t.Errorf("no error from Generate (with HasPrefix) on closed io.Writer")
   697  	}
   698  	if err := GenerateReverse(f, map[string]string{"a": "1"}, `""`); err == nil {
   699  		t.Errorf("no error from GenerateReverse on closed io.Writer")
   700  	}
   701  	if err := GenerateTest(f, "Match", "", map[string]string{"a": "1"}); err == nil {
   702  		t.Errorf("no error from GenerateTest (forward matcher) on closed io.Writer")
   703  	}
   704  	if err := GenerateTest(f, "", "MatchReverse", map[string]string{"a": "1"}); err == nil {
   705  		t.Errorf("no error from GenerateTest (reverse matcher) on closed io.Writer")
   706  	}
   707  }