123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748 |
- // Copyright 2010 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package regexp
- import (
- "bufio"
- "compress/bzip2"
- "fmt"
- "internal/testenv"
- "io"
- "os"
- "path/filepath"
- "regexp/syntax"
- "strconv"
- "strings"
- "testing"
- "unicode/utf8"
- )
- // TestRE2 tests this package's regexp API against test cases
- // considered during RE2's exhaustive tests, which run all possible
- // regexps over a given set of atoms and operators, up to a given
- // complexity, over all possible strings over a given alphabet,
- // up to a given size. Rather than try to link with RE2, we read a
- // log file containing the test cases and the expected matches.
- // The log file, re2-exhaustive.txt, is generated by running 'make log'
- // in the open source RE2 distribution https://github.com/google/re2/.
- //
- // The test file format is a sequence of stanzas like:
- //
- // strings
- // "abc"
- // "123x"
- // regexps
- // "[a-z]+"
- // 0-3;0-3
- // -;-
- // "([0-9])([0-9])([0-9])"
- // -;-
- // -;0-3 0-1 1-2 2-3
- //
- // The stanza begins by defining a set of strings, quoted
- // using Go double-quote syntax, one per line. Then the
- // regexps section gives a sequence of regexps to run on
- // the strings. In the block that follows a regexp, each line
- // gives the semicolon-separated match results of running
- // the regexp on the corresponding string.
- // Each match result is either a single -, meaning no match, or a
- // space-separated sequence of pairs giving the match and
- // submatch indices. An unmatched subexpression formats
- // its pair as a single - (not illustrated above). For now
- // each regexp run produces two match results, one for a
- // ``full match'' that restricts the regexp to matching the entire
- // string or nothing, and one for a ``partial match'' that gives
- // the leftmost first match found in the string.
- //
- // Lines beginning with # are comments. Lines beginning with
- // a capital letter are test names printed during RE2's test suite
- // and are echoed into t but otherwise ignored.
- //
- // At time of writing, re2-exhaustive.txt is 59 MB but compresses to 385 kB,
- // so we store re2-exhaustive.txt.bz2 in the repository and decompress it on the fly.
- //
- func TestRE2Search(t *testing.T) {
- testRE2(t, "testdata/re2-search.txt")
- }
- func testRE2(t *testing.T, file string) {
- f, err := os.Open(file)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- var txt io.Reader
- if strings.HasSuffix(file, ".bz2") {
- z := bzip2.NewReader(f)
- txt = z
- file = file[:len(file)-len(".bz2")] // for error messages
- } else {
- txt = f
- }
- lineno := 0
- scanner := bufio.NewScanner(txt)
- var (
- str []string
- input []string
- inStrings bool
- re *Regexp
- refull *Regexp
- nfail int
- ncase int
- )
- for lineno := 1; scanner.Scan(); lineno++ {
- line := scanner.Text()
- switch {
- case line == "":
- t.Fatalf("%s:%d: unexpected blank line", file, lineno)
- case line[0] == '#':
- continue
- case 'A' <= line[0] && line[0] <= 'Z':
- // Test name.
- t.Logf("%s\n", line)
- continue
- case line == "strings":
- str = str[:0]
- inStrings = true
- case line == "regexps":
- inStrings = false
- case line[0] == '"':
- q, err := strconv.Unquote(line)
- if err != nil {
- // Fatal because we'll get out of sync.
- t.Fatalf("%s:%d: unquote %s: %v", file, lineno, line, err)
- }
- if inStrings {
- str = append(str, q)
- continue
- }
- // Is a regexp.
- if len(input) != 0 {
- t.Fatalf("%s:%d: out of sync: have %d strings left before %#q", file, lineno, len(input), q)
- }
- re, err = tryCompile(q)
- if err != nil {
- if err.Error() == "error parsing regexp: invalid escape sequence: `\\C`" {
- // We don't and likely never will support \C; keep going.
- continue
- }
- t.Errorf("%s:%d: compile %#q: %v", file, lineno, q, err)
- if nfail++; nfail >= 100 {
- t.Fatalf("stopping after %d errors", nfail)
- }
- continue
- }
- full := `\A(?:` + q + `)\z`
- refull, err = tryCompile(full)
- if err != nil {
- // Fatal because q worked, so this should always work.
- t.Fatalf("%s:%d: compile full %#q: %v", file, lineno, full, err)
- }
- input = str
- case line[0] == '-' || '0' <= line[0] && line[0] <= '9':
- // A sequence of match results.
- ncase++
- if re == nil {
- // Failed to compile: skip results.
- continue
- }
- if len(input) == 0 {
- t.Fatalf("%s:%d: out of sync: no input remaining", file, lineno)
- }
- var text string
- text, input = input[0], input[1:]
- if !isSingleBytes(text) && strings.Contains(re.String(), `\B`) {
- // RE2's \B considers every byte position,
- // so it sees 'not word boundary' in the
- // middle of UTF-8 sequences. This package
- // only considers the positions between runes,
- // so it disagrees. Skip those cases.
- continue
- }
- res := strings.Split(line, ";")
- if len(res) != len(run) {
- t.Fatalf("%s:%d: have %d test results, want %d", file, lineno, len(res), len(run))
- }
- for i := range res {
- have, suffix := run[i](re, refull, text)
- want := parseResult(t, file, lineno, res[i])
- if !same(have, want) {
- t.Errorf("%s:%d: %#q%s.FindSubmatchIndex(%#q) = %v, want %v", file, lineno, re, suffix, text, have, want)
- if nfail++; nfail >= 100 {
- t.Fatalf("stopping after %d errors", nfail)
- }
- continue
- }
- b, suffix := match[i](re, refull, text)
- if b != (want != nil) {
- t.Errorf("%s:%d: %#q%s.MatchString(%#q) = %v, want %v", file, lineno, re, suffix, text, b, !b)
- if nfail++; nfail >= 100 {
- t.Fatalf("stopping after %d errors", nfail)
- }
- continue
- }
- }
- default:
- t.Fatalf("%s:%d: out of sync: %s\n", file, lineno, line)
- }
- }
- if err := scanner.Err(); err != nil {
- t.Fatalf("%s:%d: %v", file, lineno, err)
- }
- if len(input) != 0 {
- t.Fatalf("%s:%d: out of sync: have %d strings left at EOF", file, lineno, len(input))
- }
- t.Logf("%d cases tested", ncase)
- }
- var run = []func(*Regexp, *Regexp, string) ([]int, string){
- runFull,
- runPartial,
- runFullLongest,
- runPartialLongest,
- }
- func runFull(re, refull *Regexp, text string) ([]int, string) {
- refull.longest = false
- return refull.FindStringSubmatchIndex(text), "[full]"
- }
- func runPartial(re, refull *Regexp, text string) ([]int, string) {
- re.longest = false
- return re.FindStringSubmatchIndex(text), ""
- }
- func runFullLongest(re, refull *Regexp, text string) ([]int, string) {
- refull.longest = true
- return refull.FindStringSubmatchIndex(text), "[full,longest]"
- }
- func runPartialLongest(re, refull *Regexp, text string) ([]int, string) {
- re.longest = true
- return re.FindStringSubmatchIndex(text), "[longest]"
- }
- var match = []func(*Regexp, *Regexp, string) (bool, string){
- matchFull,
- matchPartial,
- matchFullLongest,
- matchPartialLongest,
- }
- func matchFull(re, refull *Regexp, text string) (bool, string) {
- refull.longest = false
- return refull.MatchString(text), "[full]"
- }
- func matchPartial(re, refull *Regexp, text string) (bool, string) {
- re.longest = false
- return re.MatchString(text), ""
- }
- func matchFullLongest(re, refull *Regexp, text string) (bool, string) {
- refull.longest = true
- return refull.MatchString(text), "[full,longest]"
- }
- func matchPartialLongest(re, refull *Regexp, text string) (bool, string) {
- re.longest = true
- return re.MatchString(text), "[longest]"
- }
- func isSingleBytes(s string) bool {
- for _, c := range s {
- if c >= utf8.RuneSelf {
- return false
- }
- }
- return true
- }
- func tryCompile(s string) (re *Regexp, err error) {
- // Protect against panic during Compile.
- defer func() {
- if r := recover(); r != nil {
- err = fmt.Errorf("panic: %v", r)
- }
- }()
- return Compile(s)
- }
- func parseResult(t *testing.T, file string, lineno int, res string) []int {
- // A single - indicates no match.
- if res == "-" {
- return nil
- }
- // Otherwise, a space-separated list of pairs.
- n := 1
- for j := 0; j < len(res); j++ {
- if res[j] == ' ' {
- n++
- }
- }
- out := make([]int, 2*n)
- i := 0
- n = 0
- for j := 0; j <= len(res); j++ {
- if j == len(res) || res[j] == ' ' {
- // Process a single pair. - means no submatch.
- pair := res[i:j]
- if pair == "-" {
- out[n] = -1
- out[n+1] = -1
- } else {
- loStr, hiStr, _ := strings.Cut(pair, "-")
- lo, err1 := strconv.Atoi(loStr)
- hi, err2 := strconv.Atoi(hiStr)
- if err1 != nil || err2 != nil || lo > hi {
- t.Fatalf("%s:%d: invalid pair %s", file, lineno, pair)
- }
- out[n] = lo
- out[n+1] = hi
- }
- n += 2
- i = j + 1
- }
- }
- return out
- }
- func same(x, y []int) bool {
- if len(x) != len(y) {
- return false
- }
- for i, xi := range x {
- if xi != y[i] {
- return false
- }
- }
- return true
- }
- // TestFowler runs this package's regexp API against the
- // POSIX regular expression tests collected by Glenn Fowler
- // at http://www2.research.att.com/~astopen/testregex/testregex.html.
- func TestFowler(t *testing.T) {
- files, err := filepath.Glob("testdata/*.dat")
- if err != nil {
- t.Fatal(err)
- }
- for _, file := range files {
- t.Log(file)
- testFowler(t, file)
- }
- }
- var notab = MustCompilePOSIX(`[^\t]+`)
- func testFowler(t *testing.T, file string) {
- f, err := os.Open(file)
- if err != nil {
- t.Error(err)
- return
- }
- defer f.Close()
- b := bufio.NewReader(f)
- lineno := 0
- lastRegexp := ""
- Reading:
- for {
- lineno++
- line, err := b.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- t.Errorf("%s:%d: %v", file, lineno, err)
- }
- break Reading
- }
- // http://www2.research.att.com/~astopen/man/man1/testregex.html
- //
- // INPUT FORMAT
- // Input lines may be blank, a comment beginning with #, or a test
- // specification. A specification is five fields separated by one
- // or more tabs. NULL denotes the empty string and NIL denotes the
- // 0 pointer.
- if line[0] == '#' || line[0] == '\n' {
- continue Reading
- }
- line = line[:len(line)-1]
- field := notab.FindAllString(line, -1)
- for i, f := range field {
- if f == "NULL" {
- field[i] = ""
- }
- if f == "NIL" {
- t.Logf("%s:%d: skip: %s", file, lineno, line)
- continue Reading
- }
- }
- if len(field) == 0 {
- continue Reading
- }
- // Field 1: the regex(3) flags to apply, one character per REG_feature
- // flag. The test is skipped if REG_feature is not supported by the
- // implementation. If the first character is not [BEASKLP] then the
- // specification is a global control line. One or more of [BEASKLP] may be
- // specified; the test will be repeated for each mode.
- //
- // B basic BRE (grep, ed, sed)
- // E REG_EXTENDED ERE (egrep)
- // A REG_AUGMENTED ARE (egrep with negation)
- // S REG_SHELL SRE (sh glob)
- // K REG_SHELL|REG_AUGMENTED KRE (ksh glob)
- // L REG_LITERAL LRE (fgrep)
- //
- // a REG_LEFT|REG_RIGHT implicit ^...$
- // b REG_NOTBOL lhs does not match ^
- // c REG_COMMENT ignore space and #...\n
- // d REG_SHELL_DOT explicit leading . match
- // e REG_NOTEOL rhs does not match $
- // f REG_MULTIPLE multiple \n separated patterns
- // g FNM_LEADING_DIR testfnmatch only -- match until /
- // h REG_MULTIREF multiple digit backref
- // i REG_ICASE ignore case
- // j REG_SPAN . matches \n
- // k REG_ESCAPE \ to escape [...] delimiter
- // l REG_LEFT implicit ^...
- // m REG_MINIMAL minimal match
- // n REG_NEWLINE explicit \n match
- // o REG_ENCLOSED (|&) magic inside [@|&](...)
- // p REG_SHELL_PATH explicit / match
- // q REG_DELIMITED delimited pattern
- // r REG_RIGHT implicit ...$
- // s REG_SHELL_ESCAPED \ not special
- // t REG_MUSTDELIM all delimiters must be specified
- // u standard unspecified behavior -- errors not counted
- // v REG_CLASS_ESCAPE \ special inside [...]
- // w REG_NOSUB no subexpression match array
- // x REG_LENIENT let some errors slide
- // y REG_LEFT regexec() implicit ^...
- // z REG_NULL NULL subexpressions ok
- // $ expand C \c escapes in fields 2 and 3
- // / field 2 is a regsubcomp() expression
- // = field 3 is a regdecomp() expression
- //
- // Field 1 control lines:
- //
- // C set LC_COLLATE and LC_CTYPE to locale in field 2
- //
- // ?test ... output field 5 if passed and != EXPECTED, silent otherwise
- // &test ... output field 5 if current and previous passed
- // |test ... output field 5 if current passed and previous failed
- // ; ... output field 2 if previous failed
- // {test ... skip if failed until }
- // } end of skip
- //
- // : comment comment copied as output NOTE
- // :comment:test :comment: ignored
- // N[OTE] comment comment copied as output NOTE
- // T[EST] comment comment
- //
- // number use number for nmatch (20 by default)
- flag := field[0]
- switch flag[0] {
- case '?', '&', '|', ';', '{', '}':
- // Ignore all the control operators.
- // Just run everything.
- flag = flag[1:]
- if flag == "" {
- continue Reading
- }
- case ':':
- var ok bool
- if _, flag, ok = strings.Cut(flag[1:], ":"); !ok {
- t.Logf("skip: %s", line)
- continue Reading
- }
- case 'C', 'N', 'T', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- t.Logf("skip: %s", line)
- continue Reading
- }
- // Can check field count now that we've handled the myriad comment formats.
- if len(field) < 4 {
- t.Errorf("%s:%d: too few fields: %s", file, lineno, line)
- continue Reading
- }
- // Expand C escapes (a.k.a. Go escapes).
- if strings.Contains(flag, "$") {
- f := `"` + field[1] + `"`
- if field[1], err = strconv.Unquote(f); err != nil {
- t.Errorf("%s:%d: cannot unquote %s", file, lineno, f)
- }
- f = `"` + field[2] + `"`
- if field[2], err = strconv.Unquote(f); err != nil {
- t.Errorf("%s:%d: cannot unquote %s", file, lineno, f)
- }
- }
- // Field 2: the regular expression pattern; SAME uses the pattern from
- // the previous specification.
- //
- if field[1] == "SAME" {
- field[1] = lastRegexp
- }
- lastRegexp = field[1]
- // Field 3: the string to match.
- text := field[2]
- // Field 4: the test outcome...
- ok, shouldCompile, shouldMatch, pos := parseFowlerResult(field[3])
- if !ok {
- t.Errorf("%s:%d: cannot parse result %#q", file, lineno, field[3])
- continue Reading
- }
- // Field 5: optional comment appended to the report.
- Testing:
- // Run test once for each specified capital letter mode that we support.
- for _, c := range flag {
- pattern := field[1]
- syn := syntax.POSIX | syntax.ClassNL
- switch c {
- default:
- continue Testing
- case 'E':
- // extended regexp (what we support)
- case 'L':
- // literal
- pattern = QuoteMeta(pattern)
- }
- for _, c := range flag {
- switch c {
- case 'i':
- syn |= syntax.FoldCase
- }
- }
- re, err := compile(pattern, syn, true)
- if err != nil {
- if shouldCompile {
- t.Errorf("%s:%d: %#q did not compile", file, lineno, pattern)
- }
- continue Testing
- }
- if !shouldCompile {
- t.Errorf("%s:%d: %#q should not compile", file, lineno, pattern)
- continue Testing
- }
- match := re.MatchString(text)
- if match != shouldMatch {
- t.Errorf("%s:%d: %#q.Match(%#q) = %v, want %v", file, lineno, pattern, text, match, shouldMatch)
- continue Testing
- }
- have := re.FindStringSubmatchIndex(text)
- if (len(have) > 0) != match {
- t.Errorf("%s:%d: %#q.Match(%#q) = %v, but %#q.FindSubmatchIndex(%#q) = %v", file, lineno, pattern, text, match, pattern, text, have)
- continue Testing
- }
- if len(have) > len(pos) {
- have = have[:len(pos)]
- }
- if !same(have, pos) {
- t.Errorf("%s:%d: %#q.FindSubmatchIndex(%#q) = %v, want %v", file, lineno, pattern, text, have, pos)
- }
- }
- }
- }
- func parseFowlerResult(s string) (ok, compiled, matched bool, pos []int) {
- // Field 4: the test outcome. This is either one of the posix error
- // codes (with REG_ omitted) or the match array, a list of (m,n)
- // entries with m and n being first and last+1 positions in the
- // field 3 string, or NULL if REG_NOSUB is in effect and success
- // is expected. BADPAT is acceptable in place of any regcomp(3)
- // error code. The match[] array is initialized to (-2,-2) before
- // each test. All array elements from 0 to nmatch-1 must be specified
- // in the outcome. Unspecified endpoints (offset -1) are denoted by ?.
- // Unset endpoints (offset -2) are denoted by X. {x}(o:n) denotes a
- // matched (?{...}) expression, where x is the text enclosed by {...},
- // o is the expression ordinal counting from 1, and n is the length of
- // the unmatched portion of the subject string. If x starts with a
- // number then that is the return value of re_execf(), otherwise 0 is
- // returned.
- switch {
- case s == "":
- // Match with no position information.
- ok = true
- compiled = true
- matched = true
- return
- case s == "NOMATCH":
- // Match failure.
- ok = true
- compiled = true
- matched = false
- return
- case 'A' <= s[0] && s[0] <= 'Z':
- // All the other error codes are compile errors.
- ok = true
- compiled = false
- return
- }
- compiled = true
- var x []int
- for s != "" {
- var end byte = ')'
- if len(x)%2 == 0 {
- if s[0] != '(' {
- ok = false
- return
- }
- s = s[1:]
- end = ','
- }
- i := 0
- for i < len(s) && s[i] != end {
- i++
- }
- if i == 0 || i == len(s) {
- ok = false
- return
- }
- var v = -1
- var err error
- if s[:i] != "?" {
- v, err = strconv.Atoi(s[:i])
- if err != nil {
- ok = false
- return
- }
- }
- x = append(x, v)
- s = s[i+1:]
- }
- if len(x)%2 != 0 {
- ok = false
- return
- }
- ok = true
- matched = true
- pos = x
- return
- }
- var text []byte
- func makeText(n int) []byte {
- if len(text) >= n {
- return text[:n]
- }
- text = make([]byte, n)
- x := ^uint32(0)
- for i := range text {
- x += x
- x ^= 1
- if int32(x) < 0 {
- x ^= 0x88888eef
- }
- if x%31 == 0 {
- text[i] = '\n'
- } else {
- text[i] = byte(x%(0x7E+1-0x20) + 0x20)
- }
- }
- return text
- }
- func BenchmarkMatch(b *testing.B) {
- isRaceBuilder := strings.HasSuffix(testenv.Builder(), "-race")
- for _, data := range benchData {
- r := MustCompile(data.re)
- for _, size := range benchSizes {
- if (isRaceBuilder || testing.Short()) && size.n > 1<<10 {
- continue
- }
- t := makeText(size.n)
- b.Run(data.name+"/"+size.name, func(b *testing.B) {
- b.SetBytes(int64(size.n))
- for i := 0; i < b.N; i++ {
- if r.Match(t) {
- b.Fatal("match!")
- }
- }
- })
- }
- }
- }
- func BenchmarkMatch_onepass_regex(b *testing.B) {
- isRaceBuilder := strings.HasSuffix(testenv.Builder(), "-race")
- r := MustCompile(`(?s)\A.*\z`)
- if r.onepass == nil {
- b.Fatalf("want onepass regex, but %q is not onepass", r)
- }
- for _, size := range benchSizes {
- if (isRaceBuilder || testing.Short()) && size.n > 1<<10 {
- continue
- }
- t := makeText(size.n)
- b.Run(size.name, func(b *testing.B) {
- b.SetBytes(int64(size.n))
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- if !r.Match(t) {
- b.Fatal("not match!")
- }
- }
- })
- }
- }
- var benchData = []struct{ name, re string }{
- {"Easy0", "ABCDEFGHIJKLMNOPQRSTUVWXYZ$"},
- {"Easy0i", "(?i)ABCDEFGHIJklmnopqrstuvwxyz$"},
- {"Easy1", "A[AB]B[BC]C[CD]D[DE]E[EF]F[FG]G[GH]H[HI]I[IJ]J$"},
- {"Medium", "[XYZ]ABCDEFGHIJKLMNOPQRSTUVWXYZ$"},
- {"Hard", "[ -~]*ABCDEFGHIJKLMNOPQRSTUVWXYZ$"},
- {"Hard1", "ABCD|CDEF|EFGH|GHIJ|IJKL|KLMN|MNOP|OPQR|QRST|STUV|UVWX|WXYZ"},
- }
- var benchSizes = []struct {
- name string
- n int
- }{
- {"16", 16},
- {"32", 32},
- {"1K", 1 << 10},
- {"32K", 32 << 10},
- {"1M", 1 << 20},
- {"32M", 32 << 20},
- }
- func TestLongest(t *testing.T) {
- re, err := Compile(`a(|b)`)
- if err != nil {
- t.Fatal(err)
- }
- if g, w := re.FindString("ab"), "a"; g != w {
- t.Errorf("first match was %q, want %q", g, w)
- }
- re.Longest()
- if g, w := re.FindString("ab"), "ab"; g != w {
- t.Errorf("longest match was %q, want %q", g, w)
- }
- }
- // TestProgramTooLongForBacktrack tests that a regex which is too long
- // for the backtracker still executes properly.
- func TestProgramTooLongForBacktrack(t *testing.T) {
- longRegex := MustCompile(`(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|twentyone|twentytwo|twentythree|twentyfour|twentyfive|twentysix|twentyseven|twentyeight|twentynine|thirty|thirtyone|thirtytwo|thirtythree|thirtyfour|thirtyfive|thirtysix|thirtyseven|thirtyeight|thirtynine|forty|fortyone|fortytwo|fortythree|fortyfour|fortyfive|fortysix|fortyseven|fortyeight|fortynine|fifty|fiftyone|fiftytwo|fiftythree|fiftyfour|fiftyfive|fiftysix|fiftyseven|fiftyeight|fiftynine|sixty|sixtyone|sixtytwo|sixtythree|sixtyfour|sixtyfive|sixtysix|sixtyseven|sixtyeight|sixtynine|seventy|seventyone|seventytwo|seventythree|seventyfour|seventyfive|seventysix|seventyseven|seventyeight|seventynine|eighty|eightyone|eightytwo|eightythree|eightyfour|eightyfive|eightysix|eightyseven|eightyeight|eightynine|ninety|ninetyone|ninetytwo|ninetythree|ninetyfour|ninetyfive|ninetysix|ninetyseven|ninetyeight|ninetynine|onehundred)`)
- if !longRegex.MatchString("two") {
- t.Errorf("longRegex.MatchString(\"two\") was false, want true")
- }
- if longRegex.MatchString("xxx") {
- t.Errorf("longRegex.MatchString(\"xxx\") was true, want false")
- }
- }
|