package regexp2 import ( "reflect" "strings" "testing" "time" "github.com/dlclark/regexp2/syntax" ) func TestBacktrack_CatastrophicTimeout(t *testing.T) { r, err := Compile("(.+)*\\?", 0) r.MatchTimeout = time.Millisecond * 1 t.Logf("code dump: %v", r.code.Dump()) m, err := r.FindStringMatch("Do you think you found the problem string!") if err == nil { t.Errorf("expected timeout err") } if m != nil { t.Errorf("Expected no match") } } 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(?