package regexp2
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"github.com/dlclark/regexp2/syntax"
)
func TestBacktrack_CatastrophicTimeout(t *testing.T) {
r, err := Compile("(.+)*\\?", 0)
if err != nil {
t.Fatal(err)
}
t.Logf("code dump: %v", r.code.Dump())
const subject = "Do you think you found the problem string!"
const earlyAllowance = 10 * time.Millisecond
var lateAllowance = clockPeriod + 500*time.Millisecond // Large allowance in case machine is slow
for _, timeout := range []time.Duration{
-1 * time.Millisecond,
0 * time.Millisecond,
1 * time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
500 * time.Millisecond,
1000 * time.Millisecond,
} {
t.Run(fmt.Sprint(timeout), func(t *testing.T) {
r.MatchTimeout = timeout
start := time.Now()
m, err := r.FindStringMatch(subject)
elapsed := time.Since(start)
if err == nil {
t.Errorf("expected timeout err")
}
if m != nil {
t.Errorf("Expected no match")
}
t.Logf("timeed out after %v", elapsed)
if elapsed < timeout-earlyAllowance {
t.Errorf("Match timed out too quickly (%v instead of expected %v)", elapsed, timeout-earlyAllowance)
}
if elapsed > timeout+lateAllowance {
t.Errorf("Match timed out too late (%v instead of expected %v)", elapsed, timeout+lateAllowance)
}
})
}
}
func TestSetPrefix(t *testing.T) {
r := MustCompile(`^\s*-TEST`, 0)
if r.code.FcPrefix == nil {
t.Fatalf("Expected prefix set [-\\s] but was nil")
}
if r.code.FcPrefix.PrefixSet.String() != "[-\\s]" {
t.Fatalf("Expected prefix set [\\s-] but was %v", r.code.FcPrefix.PrefixSet.String())
}
}
func TestSetInCode(t *testing.T) {
r := MustCompile(`(?
\s*(?.+))`, 0)
t.Logf("code dump: %v", r.code.Dump())
if want, got := 1, len(r.code.Sets); want != got {
t.Fatalf("r.code.Sets wanted %v, got %v", want, got)
}
if want, got := "[\\s]", r.code.Sets[0].String(); want != got {
t.Fatalf("first set wanted %v, got %v", want, got)
}
}
func TestRegexp_Basic(t *testing.T) {
r, err := Compile("test(?ing)?", 0)
//t.Logf("code dump: %v", r.code.Dump())
if err != nil {
t.Errorf("unexpected compile err: %v", err)
}
m, err := r.FindStringMatch("this is a testing stuff")
if err != nil {
t.Errorf("unexpected match err: %v", err)
}
if m == nil {
t.Error("Nil match, expected success")
} else {
//t.Logf("Match: %v", m.dump())
}
}
// check all our functions and properties around basic capture groups and referential for Group 0
func TestCapture_Basic(t *testing.T) {
r := MustCompile(`.*\B(SUCCESS)\B.*`, 0)
m, err := r.FindStringMatch("adfadsfSUCCESSadsfadsf")
if err != nil {
t.Fatalf("Unexpected match error: %v", err)
}
if m == nil {
t.Fatalf("Should have matched")
}
if want, got := "adfadsfSUCCESSadsfadsf", m.String(); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 0, m.Index; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 22, m.Length; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 1, len(m.Captures); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := m.String(), m.Captures[0].String(); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 0, m.Captures[0].Index; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 22, m.Captures[0].Length; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
g := m.Groups()
if want, got := 2, len(g); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
// group 0 is always the match
if want, got := m.String(), g[0].String(); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 1, len(g[0].Captures); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
// group 0's capture is always the match
if want, got := m.Captures[0].String(), g[0].Captures[0].String(); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
// group 1 is our first explicit group (unnamed)
if want, got := 7, g[1].Index; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := 7, g[1].Length; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
if want, got := "SUCCESS", g[1].String(); want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
}
func TestEscapeUnescape_Basic(t *testing.T) {
s1 := "#$^*+(){}<>\\|. "
s2 := Escape(s1)
s3, err := Unescape(s2)
if err != nil {
t.Fatalf("Unexpected error during unescape: %v", err)
}
//confirm one way
if want, got := `\#\$\^\*\+\(\)\{\}<>\\\|\.\ `, s2; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
//confirm round-trip
if want, got := s1, s3; want != got {
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
}
}
func TestGroups_Basic(t *testing.T) {
type d struct {
p string
s string
name []string
num []int
strs []string
}
data := []d{
d{"(?\\S+)\\s(?\\S+)", // example
"Ryan Byington",
[]string{"0", "first_name", "last_name"},
[]int{0, 1, 2},
[]string{"Ryan Byington", "Ryan", "Byington"}},
d{"((?abc)\\d+)?(?xyz)(.*)", // example
"abc208923xyzanqnakl",
[]string{"0", "1", "2", "One", "Two"},
[]int{0, 1, 2, 3, 4},
[]string{"abc208923xyzanqnakl", "abc208923", "anqnakl", "abc", "xyz"}},
d{"((?<256>abc)\\d+)?(?<16>xyz)(.*)", // numeric names
"0272saasdabc8978xyz][]12_+-",
[]string{"0", "1", "2", "16", "256"},
[]int{0, 1, 2, 16, 256},
[]string{"abc8978xyz][]12_+-", "abc8978", "][]12_+-", "xyz", "abc"}},
d{"((?<4>abc)(?\\d+))?(?<2>xyz)(?.*)", // mix numeric and string names
"0272saasdabc8978xyz][]12_+-",
[]string{"0", "1", "2", "digits", "4", "everything_else"},
[]int{0, 1, 2, 3, 4, 5},
[]string{"abc8978xyz][]12_+-", "abc8978", "xyz", "8978", "abc", "][]12_+-"}},
d{"(?\\S+)\\s(?\\S+)", // dupe string names
"Ryan Byington",
[]string{"0", "first_name"},
[]int{0, 1},
[]string{"Ryan Byington", "Byington"}},
d{"(?<15>\\S+)\\s(?<15>\\S+)", // dupe numeric names
"Ryan Byington",
[]string{"0", "15"},
[]int{0, 15},
[]string{"Ryan Byington", "Byington"}},
// *** repeated from above, but with alt cap syntax ***
d{"(?'first_name'\\S+)\\s(?'last_name'\\S+)", //example
"Ryan Byington",
[]string{"0", "first_name", "last_name"},
[]int{0, 1, 2},
[]string{"Ryan Byington", "Ryan", "Byington"}},
d{"((?'One'abc)\\d+)?(?'Two'xyz)(.*)", // example
"abc208923xyzanqnakl",
[]string{"0", "1", "2", "One", "Two"},
[]int{0, 1, 2, 3, 4},
[]string{"abc208923xyzanqnakl", "abc208923", "anqnakl", "abc", "xyz"}},
d{"((?'256'abc)\\d+)?(?'16'xyz)(.*)", // numeric names
"0272saasdabc8978xyz][]12_+-",
[]string{"0", "1", "2", "16", "256"},
[]int{0, 1, 2, 16, 256},
[]string{"abc8978xyz][]12_+-", "abc8978", "][]12_+-", "xyz", "abc"}},
d{"((?'4'abc)(?'digits'\\d+))?(?'2'xyz)(?'everything_else'.*)", // mix numeric and string names
"0272saasdabc8978xyz][]12_+-",
[]string{"0", "1", "2", "digits", "4", "everything_else"},
[]int{0, 1, 2, 3, 4, 5},
[]string{"abc8978xyz][]12_+-", "abc8978", "xyz", "8978", "abc", "][]12_+-"}},
d{"(?'first_name'\\S+)\\s(?'first_name'\\S+)", // dupe string names
"Ryan Byington",
[]string{"0", "first_name"},
[]int{0, 1},
[]string{"Ryan Byington", "Byington"}},
d{"(?'15'\\S+)\\s(?'15'\\S+)", // dupe numeric names
"Ryan Byington",
[]string{"0", "15"},
[]int{0, 15},
[]string{"Ryan Byington", "Byington"}},
}
fatalf := func(re *Regexp, v d, format string, args ...interface{}) {
args = append(args, v, re.code.Dump())
t.Fatalf(format+" using test data: %#v\ndump:%v", args...)
}
validateGroupNamesNumbers := func(re *Regexp, v d) {
if len(v.name) != len(v.num) {
fatalf(re, v, "Invalid data, group name count and number count must match")
}
groupNames := re.GetGroupNames()
if !reflect.DeepEqual(groupNames, v.name) {
fatalf(re, v, "group names expected: %v, actual: %v", v.name, groupNames)
}
groupNums := re.GetGroupNumbers()
if !reflect.DeepEqual(groupNums, v.num) {
fatalf(re, v, "group numbers expected: %v, actual: %v", v.num, groupNums)
}
// make sure we can freely get names and numbers from eachother
for i := range groupNums {
if want, got := groupNums[i], re.GroupNumberFromName(groupNames[i]); want != got {
fatalf(re, v, "group num from name Wanted '%v'\nGot '%v'", want, got)
}
if want, got := groupNames[i], re.GroupNameFromNumber(groupNums[i]); want != got {
fatalf(re, v, "group name from num Wanted '%v'\nGot '%v'", want, got)
}
}
}
for _, v := range data {
// compile the regex
re := MustCompile(v.p, 0)
// validate our group name/num info before execute
validateGroupNamesNumbers(re, v)
m, err := re.FindStringMatch(v.s)
if err != nil {
fatalf(re, v, "Unexpected error in match: %v", err)
}
if m == nil {
fatalf(re, v, "Match is nil")
}
if want, got := len(v.strs), m.GroupCount(); want != got {
fatalf(re, v, "GroupCount() Wanted '%v'\nGot '%v'", want, got)
}
g := m.Groups()
if want, got := len(v.strs), len(g); want != got {
fatalf(re, v, "len(m.Groups()) Wanted '%v'\nGot '%v'", want, got)
}
// validate each group's value from the execute
for i := range v.name {
grp1 := m.GroupByName(v.name[i])
grp2 := m.GroupByNumber(v.num[i])
// should be identical reference
if grp1 != grp2 {
fatalf(re, v, "Expected GroupByName and GroupByNumber to return same result for %v, %v", v.name[i], v.num[i])
}
if want, got := v.strs[i], grp1.String(); want != got {
fatalf(re, v, "Value[%v] Wanted '%v'\nGot '%v'", i, want, got)
}
}
// validate our group name/num info after execute
validateGroupNamesNumbers(re, v)
}
}
func TestErr_GroupName(t *testing.T) {
// group 0 is off limits
if _, err := Compile("foo(?<0>bar)", 0); err == nil {
t.Fatalf("zero group, expected error during compile")
} else if want, got := "error parsing regexp: capture number cannot be zero in `foo(?<0>bar)`", err.Error(); want != got {
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
}
if _, err := Compile("foo(?'0'bar)", 0); err == nil {
t.Fatalf("zero group, expected error during compile")
} else if want, got := "error parsing regexp: capture number cannot be zero in `foo(?'0'bar)`", err.Error(); want != got {
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
}
// group tag can't start with a num
if _, err := Compile("foo(?<1bar>)", 0); err == nil {
t.Fatalf("invalid group name, expected error during compile")
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?<1bar>)`", err.Error(); want != got {
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
}
if _, err := Compile("foo(?'1bar')", 0); err == nil {
t.Fatalf("invalid group name, expected error during compile")
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?'1bar')`", err.Error(); want != got {
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
}
// missing closing group tag
if _, err := Compile("foo(?`, ECMAScript)
if m, err := re.MatchString(`k`); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
_, err := Compile(`(?\w+), yes \k'title'`, ECMAScript)
if err == nil {
t.Fatal("Expected error")
}
re = MustCompile(`(?\w+), yes \k`, ECMAScript)
if m, err := re.MatchString("sir, yes sir"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
re = MustCompile(`\k, yes (?\w+)`, ECMAScript)
if m, err := re.MatchString(", yes sir"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
_, err = Compile(`\k<(?)>`, ECMAScript)
if err == nil {
t.Fatal("Expected error")
}
MustCompile(`\k<()>`, ECMAScript)
_, err = Compile(`\k<()>`, 0)
if err == nil {
t.Fatal("Expected error")
}
re = MustCompile(`\'|\`, 0)
if m, err := re.MatchString("'"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
if m, err := re.MatchString("<"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestECMAInvalidEscapeCharClass(t *testing.T) {
re := MustCompile(`[\x0]`, ECMAScript)
if m, err := re.MatchString("x"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
if m, err := re.MatchString("0"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
if m, err := re.MatchString("z"); err != nil {
t.Fatal(err)
} else if m {
t.Fatal("Expected no match")
}
}
func TestECMAScriptXCurlyBraceEscape(t *testing.T) {
re := MustCompile(`\x{20}`, ECMAScript)
if m, err := re.MatchString(" "); err != nil {
t.Fatal(err)
} else if m {
t.Fatal("Expected no match")
}
if m, err := re.MatchString("xxxxxxxxxxxxxxxxxxxx"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestEcmaScriptUnicodeRange(t *testing.T) {
r, err := Compile(`([\u{001a}-\u{ffff}]+)`, ECMAScript|Unicode)
if err != nil {
panic(err)
}
m, err := r.FindStringMatch("qqqq")
if err != nil {
panic(err)
}
if m == nil {
t.Fatal("Expected non-nil, got nil")
}
}
func TestNegateRange(t *testing.T) {
re := MustCompile(`[\D]`, 0)
if m, err := re.MatchString("A"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestECMANegateRange(t *testing.T) {
re := MustCompile(`[\D]`, ECMAScript)
if m, err := re.MatchString("A"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestDollar(t *testing.T) {
// PCRE/C# allow \n to match to $ at end-of-string in singleline mode...
// a weird edge-case kept for compatibility, ECMAScript/RE2 mode don't allow it
re := MustCompile(`ac$`, 0)
if m, err := re.MatchString("ac\n"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestECMADollar(t *testing.T) {
re := MustCompile(`ac$`, ECMAScript)
if m, err := re.MatchString("ac\n"); err != nil {
t.Fatal(err)
} else if m {
t.Fatal("Expected no match")
}
}
func TestThreeByteUnicode_InputOnly(t *testing.T) {
// confirm the bmprefix properly ignores 3-byte unicode in the input value
// this used to panic
re := MustCompile("高", 0)
if m, err := re.MatchString("📍Test高"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestMultibyteUnicode_MatchPartialPattern(t *testing.T) {
re := MustCompile("猟な", 0)
if m, err := re.MatchString("なあ🍺な"); err != nil {
t.Fatal(err)
} else if m {
t.Fatal("Expected no match")
}
}
func TestMultibyteUnicode_Match(t *testing.T) {
re := MustCompile("猟な", 0)
if m, err := re.MatchString("なあ🍺猟な"); err != nil {
t.Fatal(err)
} else if !m {
t.Fatal("Expected match")
}
}
func TestAlternationNamedOptions_Errors(t *testing.T) {
// all of these should give an error "error parsing regexp:"
data := []string{
"(?(?e))", "(?(?a)", "(?(?", "(?(", "?(a:b)", "?(a)", "?(a|b)", "?((a)", "?((a)a", "?((a)a|", "?((a)a|b",
"(?(?i))", "(?(?I))", "(?(?m))", "(?(?M))", "(?(?s))", "(?(?S))", "(?(?x))", "(?(?X))", "(?(?n))", "(?(?N))", " (?(?n))",
}
for _, p := range data {
re, err := Compile(p, 0)
if err == nil {
t.Fatal("Expected error, got nil")
}
if re != nil {
t.Fatal("Expected unparsed regexp, got non-nil")
}
if !strings.HasPrefix(err.Error(), "error parsing regexp: ") {
t.Fatalf("Wanted parse error, got '%v'", err)
}
}
}
func TestAlternationNamedOptions_Success(t *testing.T) {
data := []struct {
pattern string
input string
expectSuccess bool
matchVal string
}{
{"(?(cat)|dog)", "cat", true, ""},
{"(?(cat)|dog)", "catdog", true, ""},
{"(?(cat)dog1|dog2)", "catdog1", false, ""},
{"(?(cat)dog1|dog2)", "catdog2", true, "dog2"},
{"(?(cat)dog1|dog2)", "catdog1dog2", true, "dog2"},
{"(?(dog2))", "dog2", true, ""},
{"(?(cat)|dog)", "oof", false, ""},
{"(?(a:b))", "a", true, ""},
{"(?(a:))", "a", true, ""},
}
for _, p := range data {
re := MustCompile(p.pattern, 0)
m, err := re.FindStringMatch(p.input)
if err != nil {
t.Fatalf("Unexpected error during match: %v", err)
}
if want, got := p.expectSuccess, m != nil; want != got {
t.Fatalf("Success mismatch for %v, wanted %v, got %v", p.pattern, want, got)
}
if m != nil {
if want, got := p.matchVal, m.String(); want != got {
t.Fatalf("Match val mismatch for %v, wanted %v, got %v", p.pattern, want, got)
}
}
}
}
func TestAlternationConstruct_Matches(t *testing.T) {
re := MustCompile("(?(A)A123|C789)", 0)
m, err := re.FindStringMatch("A123 B456 C789")
if err != nil {
t.Fatalf("Unexpected err: %v", err)
}
if m == nil {
t.Fatal("Expected match, got nil")
}
if want, got := "A123", m.String(); want != got {
t.Fatalf("Wanted %v, got %v", want, got)
}
m, err = re.FindNextMatch(m)
if err != nil {
t.Fatalf("Unexpected err in second match: %v", err)
}
if m == nil {
t.Fatal("Expected second match, got nil")
}
if want, got := "C789", m.String(); want != got {
t.Fatalf("Wanted %v, got %v", want, got)
}
m, err = re.FindNextMatch(m)
if err != nil {
t.Fatalf("Unexpected err in third match: %v", err)
}
if m != nil {
t.Fatal("Did not expect third match")
}
}
func TestStartAtEnd(t *testing.T) {
re := MustCompile("(?:)", 0)
m, err := re.FindStringMatchStartingAt("t", 1)
if err != nil {
t.Fatal(err)
}
if m == nil {
t.Fatal("Expected match")
}
}
func TestParserFuzzCrashes(t *testing.T) {
var crashes = []string{
"(?'-", "(\\c0)", "(\\00(?())", "[\\p{0}", "(\x00?.*.()?(()?)?)*.x\xcb?&(\\s\x80)", "\\p{0}", "[0-[\\p{0}",
}
for _, c := range crashes {
t.Log(c)
Compile(c, 0)
}
}
func TestParserFuzzHangs(t *testing.T) {
var hangs = []string{
"\r{865720113}z\xd5{\r{861o", "\r{915355}\r{9153}", "\r{525005}", "\x01{19765625}", "(\r{068828256})", "\r{677525005}",
}
for _, c := range hangs {
t.Log(c)
Compile(c, 0)
}
}
func BenchmarkParserPrefixLongLen(b *testing.B) {
re := MustCompile("\r{100001}T+", 0)
inp := strings.Repeat("testing", 10000) + strings.Repeat("\r", 100000) + "TTTT"
b.ResetTimer()
for i := 0; i < b.N; i++ {
if m, err := re.MatchString(inp); err != nil {
b.Fatalf("Unexpected err: %v", err)
} else if m {
b.Fatalf("Expected no match")
}
}
}
/*
func TestPcreStuff(t *testing.T) {
re := MustCompile(`(?(?=(a))a)`, Debug)
inp := unEscapeToMatch(`a`)
fmt.Printf("Inp %q\n", inp)
m, err := re.FindStringMatch(inp)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if m == nil {
t.Fatalf("Expected match")
}
fmt.Printf("Match %s\n", m.dump())
fmt.Printf("Text: %v\n", unEscapeGroup(m.String()))
}
*/
//(.*)(\d+) different FirstChars ([\x00-\t\v-\x08] OR [\x00-\t\v-\uffff\p{Nd}]
func TestControlBracketFail(t *testing.T) {
re := MustCompile(`(cat)(\c[*)(dog)`, 0)
inp := "asdlkcat\u00FFdogiwod"
if m, _ := re.MatchString(inp); m {
t.Fatal("expected no match")
}
}
func TestControlBracketGroups(t *testing.T) {
re := MustCompile(`(cat)(\c[*)(dog)`, 0)
inp := "asdlkcat\u001bdogiwod"
if want, got := 4, re.capsize; want != got {
t.Fatalf("Capsize wrong, want %v, got %v", want, got)
}
m, _ := re.FindStringMatch(inp)
if m == nil {
t.Fatal("expected match")
}
g := m.Groups()
want := []string{"cat\u001bdog", "cat", "\u001b", "dog"}
for i := 0; i < len(g); i++ {
if want[i] != g[i].String() {
t.Fatalf("Bad group num %v, want %v, got %v", i, want[i], g[i].String())
}
}
}
func TestBadGroupConstruct(t *testing.T) {
bad := []string{"(?>-", "(?<", "(?<=", "(?", "(?)", "(?<)", "(?')", "(?<-"}
for _, b := range bad {
_, err := Compile(b, 0)
if err == nil {
t.Fatalf("Wanted error, but got no error for pattern: %v", b)
}
}
}
func TestEmptyCaptureLargeRepeat(t *testing.T) {
// a bug would cause our track to not grow and eventually panic
// with large numbers of repeats of a non-capturing group (>16)
// the issue was that the jump occured to the same statement over and over
// and the "grow stack/track" logic only triggered on jumps that moved
// backwards
r := MustCompile(`(?:){40}`, 0)
m, err := r.FindStringMatch("1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if want, got := 0, m.Index; want != got {
t.Errorf("First Match Index wanted %v got %v", want, got)
}
if want, got := 0, m.Length; want != got {
t.Errorf("First Match Length wanted %v got %v", want, got)
}
m, _ = r.FindNextMatch(m)
if want, got := 1, m.Index; want != got {
t.Errorf("Second Match Index wanted %v got %v", want, got)
}
if want, got := 0, m.Length; want != got {
t.Errorf("Second Match Length wanted %v got %v", want, got)
}
m, _ = r.FindNextMatch(m)
if m != nil {
t.Fatal("Expected 2 matches, got more")
}
}
func TestFuzzBytes_NoCompile(t *testing.T) {
//some crash cases found from fuzzing
var testCases = []struct {
r []byte
}{
{
r: []byte{0x28, 0x28, 0x29, 0x5c, 0x37, 0x28, 0x3f, 0x28, 0x29, 0x29},
},
{
r: []byte{0x28, 0x5c, 0x32, 0x28, 0x3f, 0x28, 0x30, 0x29, 0x29},
},
{
r: []byte{0x28, 0x3f, 0x28, 0x29, 0x29, 0x5c, 0x31, 0x30, 0x28, 0x3f, 0x28, 0x30, 0x29},
},
{
r: []byte{0x28, 0x29, 0x28, 0x28, 0x29, 0x5c, 0x37, 0x28, 0x3f, 0x28, 0x29, 0x29},
},
}
for _, c := range testCases {
r := string(c.r)
t.Run(r, func(t *testing.T) {
_, err := Compile(r, Multiline|ECMAScript|Debug)
// should fail compiling
if err == nil {
t.Fatal("should fail compile, but didn't")
}
})
}
}
func TestFuzzBytes_Match(t *testing.T) {
var testCases = []struct {
r, s []byte
}{
{
r: []byte{0x30, 0xbf, 0x30, 0x2a, 0x30, 0x30},
s: []byte{0xf0, 0xb0, 0x80, 0x91, 0xf7},
},
{
r: []byte{0x30, 0xaf, 0xf3, 0x30, 0x2a},
s: []byte{0xf3, 0x80, 0x80, 0x87, 0x80, 0x89},
},
}
for _, c := range testCases {
r := string(c.r)
t.Run(r, func(t *testing.T) {
re, err := Compile(r, 0)
if err != nil {
t.Fatal("should compile, but didn't")
}
re.MatchString(string(c.s))
})
}
}
func TestConcatAccidentalPatternCharge(t *testing.T) {
// originally this pattern would parse incorrectly
// specifically the closing group would concat the string literals
// together but the raw rune slice would blow over the original pattern
// so the final bit of pattern parsing would be wrong
// fixed in #49
r, err := Compile(`(?<=1234\.\*56).*(?=890)`, 0)
if err != nil {
panic(err)
}
m, err := r.FindStringMatch(`1234.*567890`)
if err != nil {
panic(err)
}
if m == nil {
t.Fatal("Expected non-nil, got nil")
}
}
func TestGoodReverseOrderMessage(t *testing.T) {
_, err := Compile(`[h-c]`, ECMAScript)
if err == nil {
t.Fatal("expected error")
}
expected := "error parsing regexp: [h-c] range in reverse order in `[h-c]`"
if err.Error() != expected {
t.Fatalf("expected %q got %q", expected, err.Error())
}
}
func TestParseShortSlashP(t *testing.T) {
re := MustCompile(`[!\pL\pN]{1,}`, 0)
m, err := re.FindStringMatch("this23! is a! test 1a 2b")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if m.String() != "this23!" {
t.Fatalf("Expected match")
}
}
func TestParseShortSlashNegateP(t *testing.T) {
re := MustCompile(`\PNa`, 0)
m, err := re.FindStringMatch("this is a test 1a 2b")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if m.String() != " a" {
t.Fatalf("Expected match")
}
}
func TestParseShortSlashPEnd(t *testing.T) {
re := MustCompile(`\pN`, 0)
m, err := re.FindStringMatch("this is a test 1a 2b")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if m.String() != "1" {
t.Fatalf("Expected match")
}
}