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(`(?<title>\w+), yes \k<title>`, ECMAScript) if m, err := re.MatchString("sir, yes sir"); err != nil { t.Fatal(err) } else if !m { t.Fatal("Expected match") } re = MustCompile(`\k<title>, yes (?<title>\w+)`, ECMAScript) if m, err := re.MatchString(", yes sir"); err != nil { t.Fatal(err) } else if !m { t.Fatal("Expected match") } _, err = Compile(`\k<(?<name>)>`, ECMAScript) if err == nil { t.Fatal("Expected error") } MustCompile(`\k<(<name>)>`, ECMAScript) _, err = Compile(`\k<(<name>)>`, 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") } }