Forked the emersion/go-imap v1 project.

This commit is contained in:
2025-05-01 11:58:18 +03:00
commit bcc3f95e8e
107 changed files with 16268 additions and 0 deletions

306
internal/imapnum/numset.go Normal file
View File

@@ -0,0 +1,306 @@
package imapnum
import (
"fmt"
"strconv"
"strings"
)
// Range represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
// represented by setting Start = Stop. Zero is used to represent "*", which is
// safe because seq-number uses nz-number rule. The order of values is always
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
type Range struct {
Start, Stop uint32
}
// Contains returns true if the seq-number q is contained in range value s.
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
// contains "*" and all numbers >= n.
func (s Range) Contains(q uint32) bool {
if q == 0 {
return s.Stop == 0 // "*" is contained only in "*" and "n:*"
}
return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
}
// Less returns true if s precedes and does not contain seq-number q.
func (s Range) Less(q uint32) bool {
return (s.Stop < q || q == 0) && s.Stop != 0
}
// Merge combines range values s and t into a single union if the two
// intersect or one is a superset of the other. The order of s and t does not
// matter. If the values cannot be merged, s is returned unmodified and ok is
// set to false.
func (s Range) Merge(t Range) (union Range, ok bool) {
union = s
if s == t {
return s, true
}
if s.Start != 0 && t.Start != 0 {
// s and t are any combination of "n", "n:m", or "n:*"
if s.Start > t.Start {
s, t = t, s
}
// s starts at or before t, check where it ends
if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
return s, true // s is a superset of t
}
// s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
return Range{s.Start, t.Stop}, true // s intersects or touches t
}
return union, false
}
// exactly one of s and t is "*"
if s.Start == 0 {
if t.Stop == 0 {
return t, true // s is "*", t is "n:*"
}
} else if s.Stop == 0 {
return s, true // s is "n:*", t is "*"
}
return union, false
}
// String returns range value s as a seq-number or seq-range string.
func (s Range) String() string {
if s.Start == s.Stop {
if s.Start == 0 {
return "*"
}
return strconv.FormatUint(uint64(s.Start), 10)
}
b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
if s.Stop == 0 {
return string(append(b, ':', '*'))
}
return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
}
func (s Range) append(nums []uint32) (out []uint32, ok bool) {
if s.Start == 0 || s.Stop == 0 {
return nil, false
}
for n := s.Start; n <= s.Stop; n++ {
nums = append(nums, n)
}
return nums, true
}
// Set is used to represent a set of message sequence numbers or UIDs (see
// sequence-set ABNF rule). The zero value is an empty set.
type Set []Range
// AddNum inserts new numbers into the set. The value 0 represents "*".
func (s *Set) AddNum(q ...uint32) {
for _, v := range q {
s.insert(Range{v, v})
}
}
// AddRange inserts a new range into the set.
func (s *Set) AddRange(start, stop uint32) {
if (stop < start && stop != 0) || start == 0 {
s.insert(Range{stop, start})
} else {
s.insert(Range{start, stop})
}
}
// AddSet inserts all values from t into s.
func (s *Set) AddSet(t Set) {
for _, v := range t {
s.insert(v)
}
}
// Dynamic returns true if the set contains "*" or "n:*" values.
func (s Set) Dynamic() bool {
return len(s) > 0 && s[len(s)-1].Stop == 0
}
// Contains returns true if the non-zero sequence number or UID q is contained
// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
// responsibility to handle the special case where q is the maximum UID in the
// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
// it doesn't know what the maximum value is).
func (s Set) Contains(q uint32) bool {
if _, ok := s.search(q); ok {
return q != 0
}
return false
}
// Nums returns a slice of all numbers contained in the set.
func (s Set) Nums() (nums []uint32, ok bool) {
for _, v := range s {
nums, ok = v.append(nums)
if !ok {
return nil, false
}
}
return nums, true
}
// String returns a sorted representation of all contained number values.
func (s Set) String() string {
if len(s) == 0 {
return ""
}
b := make([]byte, 0, 64)
for _, v := range s {
b = append(b, ',')
if v.Start == 0 {
b = append(b, '*')
continue
}
b = strconv.AppendUint(b, uint64(v.Start), 10)
if v.Start != v.Stop {
if v.Stop == 0 {
b = append(b, ':', '*')
continue
}
b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
}
}
return string(b[1:])
}
// insert adds range value v to the set.
func (ptr *Set) insert(v Range) {
s := *ptr
defer func() {
*ptr = s
}()
i, _ := s.search(v.Start)
merged := false
if i > 0 {
// try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
s[i-1], merged = s[i-1].Merge(v)
}
if i == len(s) {
// v was either merged with the last entry or needs to be appended
if !merged {
s.insertAt(i, v)
}
return
} else if merged {
i--
} else if s[i], merged = s[i].Merge(v); !merged {
s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
return
}
// v was merged with s[i], continue trying to merge until the end
for j := i + 1; j < len(s); j++ {
if s[i], merged = s[i].Merge(s[j]); !merged {
if j > i+1 {
// cut out all entries between i and j that were merged
s = append(s[:i+1], s[j:]...)
}
return
}
}
// everything after s[i] was merged
s = s[:i+1]
}
// insertAt inserts a new range value v at index i, resizing s.Set as needed.
func (ptr *Set) insertAt(i int, v Range) {
s := *ptr
defer func() {
*ptr = s
}()
if n := len(s); i == n {
// insert at the end
s = append(s, v)
return
} else if n < cap(s) {
// enough space, shift everything at and after i to the right
s = s[:n+1]
copy(s[i+1:], s[i:])
} else {
// allocate new slice and copy everything, n is at least 1
set := make([]Range, n+1, n*2)
copy(set, s[:i])
copy(set[i+1:], s[i:])
s = set
}
s[i] = v
}
// search attempts to find the index of the range set value that contains q.
// If no values contain q, the returned index is the position where q should be
// inserted and ok is set to false.
func (s Set) search(q uint32) (i int, ok bool) {
min, max := 0, len(s)-1
for min < max {
if mid := (min + max) >> 1; s[mid].Less(q) {
min = mid + 1
} else {
max = mid
}
}
if max < 0 || s[min].Less(q) {
return len(s), false // q is the new largest value
}
return min, s[min].Contains(q)
}
// errBadNumSet is used to report problems with the format of a number set
// value.
type errBadNumSet string
func (err errBadNumSet) Error() string {
return fmt.Sprintf("imap: bad number set value %q", string(err))
}
// parseNum parses a single seq-number value (non-zero uint32 or "*").
func parseNum(v string) (uint32, error) {
if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
return uint32(n), nil
} else if v == "*" {
return 0, nil
}
return 0, errBadNumSet(v)
}
// parseNumRange creates a new seq instance by parsing strings in the format
// "n" or "n:m", where n and/or m may be "*". An error is returned for invalid
// values.
func parseNumRange(v string) (Range, error) {
var (
r Range
err error
)
if sep := strings.IndexRune(v, ':'); sep < 0 {
r.Start, err = parseNum(v)
r.Stop = r.Start
return r, err
} else if r.Start, err = parseNum(v[:sep]); err == nil {
if r.Stop, err = parseNum(v[sep+1:]); err == nil {
if (r.Stop < r.Start && r.Stop != 0) || r.Start == 0 {
r.Start, r.Stop = r.Stop, r.Start
}
return r, nil
}
}
return r, errBadNumSet(v)
}
// ParseSet returns a new Set after parsing the set string.
func ParseSet(set string) (Set, error) {
var s Set
for _, sv := range strings.Split(set, ",") {
r, err := parseNumRange(sv)
if err != nil {
return s, err
}
s.AddRange(r.Start, r.Stop)
}
return s, nil
}

View File

@@ -0,0 +1,724 @@
package imapnum
import (
"math/rand"
"strings"
"testing"
)
const max = ^uint32(0)
func TestParseNumRange(t *testing.T) {
tests := []struct {
in string
out Range
ok bool
}{
// Invalid number
{"", Range{}, false},
{" ", Range{}, false},
{"A", Range{}, false},
{"0", Range{}, false},
{" 1", Range{}, false},
{"1 ", Range{}, false},
{"*1", Range{}, false},
{"1*", Range{}, false},
{"-1", Range{}, false},
{"01", Range{}, false},
{"0x1", Range{}, false},
{"1 2", Range{}, false},
{"1,2", Range{}, false},
{"1.2", Range{}, false},
{"4294967296", Range{}, false},
// Valid number
{"*", Range{0, 0}, true},
{"1", Range{1, 1}, true},
{"42", Range{42, 42}, true},
{"1000", Range{1000, 1000}, true},
{"4294967295", Range{max, max}, true},
// Invalid range
{":", Range{}, false},
{"*:", Range{}, false},
{":*", Range{}, false},
{"1:", Range{}, false},
{":1", Range{}, false},
{"0:0", Range{}, false},
{"0:*", Range{}, false},
{"0:1", Range{}, false},
{"1:0", Range{}, false},
{"1:2 ", Range{}, false},
{"1: 2", Range{}, false},
{"1:2:", Range{}, false},
{"1:2,", Range{}, false},
{"1:2:3", Range{}, false},
{"1:2,3", Range{}, false},
{"*:4294967296", Range{}, false},
{"0:4294967295", Range{}, false},
{"1:4294967296", Range{}, false},
{"4294967296:*", Range{}, false},
{"4294967295:0", Range{}, false},
{"4294967296:1", Range{}, false},
{"4294967295:4294967296", Range{}, false},
// Valid range
{"*:*", Range{0, 0}, true},
{"1:*", Range{1, 0}, true},
{"*:1", Range{1, 0}, true},
{"2:2", Range{2, 2}, true},
{"2:42", Range{2, 42}, true},
{"42:2", Range{2, 42}, true},
{"*:4294967294", Range{max - 1, 0}, true},
{"*:4294967295", Range{max, 0}, true},
{"4294967294:*", Range{max - 1, 0}, true},
{"4294967295:*", Range{max, 0}, true},
{"1:4294967294", Range{1, max - 1}, true},
{"1:4294967295", Range{1, max}, true},
{"4294967295:1000", Range{1000, max}, true},
{"4294967294:4294967295", Range{max - 1, max}, true},
{"4294967295:4294967295", Range{max, max}, true},
}
for _, test := range tests {
out, err := parseNumRange(test.in)
if !test.ok {
if err == nil {
t.Errorf("parseSeq(%q) expected error; got %q", test.in, out)
}
} else if err != nil {
t.Errorf("parseSeq(%q) expected %q; got %v", test.in, test.out, err)
} else if out != test.out {
t.Errorf("parseSeq(%q) expected %q; got %q", test.in, test.out, out)
}
}
}
func TestNumRangeContainsLess(t *testing.T) {
tests := []struct {
s string
q uint32
contains bool
less bool
}{
{"2", 0, false, true},
{"2", 1, false, false},
{"2", 2, true, false},
{"2", 3, false, true},
{"2", max, false, true},
{"*", 0, true, false},
{"*", 1, false, false},
{"*", 2, false, false},
{"*", 3, false, false},
{"*", max, false, false},
{"2:3", 0, false, true},
{"2:3", 1, false, false},
{"2:3", 2, true, false},
{"2:3", 3, true, false},
{"2:3", 4, false, true},
{"2:3", 5, false, true},
{"2:4", 0, false, true},
{"2:4", 1, false, false},
{"2:4", 2, true, false},
{"2:4", 3, true, false},
{"2:4", 4, true, false},
{"2:4", 5, false, true},
{"4:4294967295", 0, false, true},
{"4:4294967295", 1, false, false},
{"4:4294967295", 2, false, false},
{"4:4294967295", 3, false, false},
{"4:4294967295", 4, true, false},
{"4:4294967295", 5, true, false},
{"4:4294967295", max, true, false},
{"4:*", 0, true, false},
{"4:*", 1, false, false},
{"4:*", 2, false, false},
{"4:*", 3, false, false},
{"4:*", 4, true, false},
{"4:*", 5, true, false},
{"4:*", max, true, false},
}
for _, test := range tests {
s, err := parseNumRange(test.s)
if err != nil {
t.Errorf("parseSeq(%q) unexpected error; %v", test.s, err)
continue
}
if s.Contains(test.q) != test.contains {
t.Errorf("%q.Contains(%d) expected %v", test.s, test.q, test.contains)
}
if s.Less(test.q) != test.less {
t.Errorf("%q.Less(%d) expected %v", test.s, test.q, test.less)
}
}
}
func TestNumRangeMerge(T *testing.T) {
tests := []struct {
s, t, out string
}{
// Number with number
{"1", "1", "1"},
{"1", "2", "1:2"},
{"1", "3", ""},
{"1", "4294967295", ""},
{"1", "*", ""},
{"4", "1", ""},
{"4", "2", ""},
{"4", "3", "3:4"},
{"4", "4", "4"},
{"4", "5", "4:5"},
{"4", "6", ""},
{"4294967295", "4294967293", ""},
{"4294967295", "4294967294", "4294967294:4294967295"},
{"4294967295", "4294967295", "4294967295"},
{"4294967295", "*", ""},
{"*", "1", ""},
{"*", "2", ""},
{"*", "4294967294", ""},
{"*", "4294967295", ""},
{"*", "*", "*"},
// Range with number
{"1:3", "1", "1:3"},
{"1:3", "2", "1:3"},
{"1:3", "3", "1:3"},
{"1:3", "4", "1:4"},
{"1:3", "5", ""},
{"1:3", "*", ""},
{"3:4", "1", ""},
{"3:4", "2", "2:4"},
{"3:4", "3", "3:4"},
{"3:4", "4", "3:4"},
{"3:4", "5", "3:5"},
{"3:4", "6", ""},
{"3:4", "*", ""},
{"2:3", "5", ""},
{"2:4", "5", "2:5"},
{"2:5", "5", "2:5"},
{"2:6", "5", "2:6"},
{"2:7", "5", "2:7"},
{"2:*", "5", "2:*"},
{"3:4", "5", "3:5"},
{"3:5", "5", "3:5"},
{"3:6", "5", "3:6"},
{"3:7", "5", "3:7"},
{"3:*", "5", "3:*"},
{"4:5", "5", "4:5"},
{"4:6", "5", "4:6"},
{"4:7", "5", "4:7"},
{"4:*", "5", "4:*"},
{"5:6", "5", "5:6"},
{"5:7", "5", "5:7"},
{"5:*", "5", "5:*"},
{"6:7", "5", "5:7"},
{"6:*", "5", "5:*"},
{"7:8", "5", ""},
{"7:*", "5", ""},
{"3:4294967294", "1", ""},
{"3:4294967294", "2", "2:4294967294"},
{"3:4294967294", "3", "3:4294967294"},
{"3:4294967294", "4", "3:4294967294"},
{"3:4294967294", "4294967293", "3:4294967294"},
{"3:4294967294", "4294967294", "3:4294967294"},
{"3:4294967294", "4294967295", "3:4294967295"},
{"3:4294967294", "*", ""},
{"3:4294967295", "1", ""},
{"3:4294967295", "2", "2:4294967295"},
{"3:4294967295", "3", "3:4294967295"},
{"3:4294967295", "4", "3:4294967295"},
{"3:4294967295", "4294967294", "3:4294967295"},
{"3:4294967295", "4294967295", "3:4294967295"},
{"3:4294967295", "*", ""},
{"1:4294967295", "1", "1:4294967295"},
{"1:4294967295", "4294967295", "1:4294967295"},
{"1:4294967295", "*", ""},
{"1:*", "1", "1:*"},
{"1:*", "2", "1:*"},
{"1:*", "4294967294", "1:*"},
{"1:*", "4294967295", "1:*"},
{"1:*", "*", "1:*"},
// Range with range
{"5:8", "1:2", ""},
{"5:8", "1:3", ""},
{"5:8", "1:4", "1:8"},
{"5:8", "1:5", "1:8"},
{"5:8", "1:6", "1:8"},
{"5:8", "1:7", "1:8"},
{"5:8", "1:8", "1:8"},
{"5:8", "1:9", "1:9"},
{"5:8", "1:10", "1:10"},
{"5:8", "1:11", "1:11"},
{"5:8", "1:*", "1:*"},
{"5:8", "2:3", ""},
{"5:8", "2:4", "2:8"},
{"5:8", "2:5", "2:8"},
{"5:8", "2:6", "2:8"},
{"5:8", "2:7", "2:8"},
{"5:8", "2:8", "2:8"},
{"5:8", "2:9", "2:9"},
{"5:8", "2:10", "2:10"},
{"5:8", "2:11", "2:11"},
{"5:8", "2:*", "2:*"},
{"5:8", "3:4", "3:8"},
{"5:8", "3:5", "3:8"},
{"5:8", "3:6", "3:8"},
{"5:8", "3:7", "3:8"},
{"5:8", "3:8", "3:8"},
{"5:8", "3:9", "3:9"},
{"5:8", "3:10", "3:10"},
{"5:8", "3:11", "3:11"},
{"5:8", "3:*", "3:*"},
{"5:8", "4:5", "4:8"},
{"5:8", "4:6", "4:8"},
{"5:8", "4:7", "4:8"},
{"5:8", "4:8", "4:8"},
{"5:8", "4:9", "4:9"},
{"5:8", "4:10", "4:10"},
{"5:8", "4:11", "4:11"},
{"5:8", "4:*", "4:*"},
{"5:8", "5:6", "5:8"},
{"5:8", "5:7", "5:8"},
{"5:8", "5:8", "5:8"},
{"5:8", "5:9", "5:9"},
{"5:8", "5:10", "5:10"},
{"5:8", "5:11", "5:11"},
{"5:8", "5:*", "5:*"},
{"5:8", "6:7", "5:8"},
{"5:8", "6:8", "5:8"},
{"5:8", "6:9", "5:9"},
{"5:8", "6:10", "5:10"},
{"5:8", "6:11", "5:11"},
{"5:8", "6:*", "5:*"},
{"5:8", "7:8", "5:8"},
{"5:8", "7:9", "5:9"},
{"5:8", "7:10", "5:10"},
{"5:8", "7:11", "5:11"},
{"5:8", "7:*", "5:*"},
{"5:8", "8:9", "5:9"},
{"5:8", "8:10", "5:10"},
{"5:8", "8:11", "5:11"},
{"5:8", "8:*", "5:*"},
{"5:8", "9:10", "5:10"},
{"5:8", "9:11", "5:11"},
{"5:8", "9:*", "5:*"},
{"5:8", "10:11", ""},
{"5:8", "10:*", ""},
{"1:*", "1:*", "1:*"},
{"1:*", "2:*", "1:*"},
{"1:*", "1:4294967294", "1:*"},
{"1:*", "1:4294967295", "1:*"},
{"1:*", "2:4294967295", "1:*"},
{"1:4294967295", "1:4294967294", "1:4294967295"},
{"1:4294967295", "1:4294967295", "1:4294967295"},
{"1:4294967295", "2:4294967295", "1:4294967295"},
{"1:4294967295", "2:*", "1:*"},
}
for _, test := range tests {
s, err := parseNumRange(test.s)
if err != nil {
T.Errorf("parseSeq(%q) unexpected error; %v", test.s, err)
continue
}
t, err := parseNumRange(test.t)
if err != nil {
T.Errorf("parseSeq(%q) unexpected error; %v", test.t, err)
continue
}
testOK := test.out != ""
for i := 0; i < 2; i++ {
if !testOK {
test.out = test.s
}
out, ok := s.Merge(t)
if out.String() != test.out || ok != testOK {
T.Errorf("%q.Merge(%q) expected %q; got %q", test.s, test.t, test.out, out)
}
// Swap s & t, result should be identical
test.s, test.t = test.t, test.s
s, t = t, s
}
}
}
func checkNumSet(s Set, t *testing.T) {
n := len(s)
for i, v := range s {
if v.Start == 0 {
if v.Stop != 0 {
t.Errorf(`NumSet(%q) index %d: "*:n" range`, s, i)
} else if i != n-1 {
t.Errorf(`NumSet(%q) index %d: "*" not at the end`, s, i)
}
continue
}
if i > 0 && s[i-1].Stop >= v.Start-1 {
t.Errorf(`NumSet(%q) index %d: overlap`, s, i)
}
if v.Stop < v.Start {
if v.Stop != 0 {
t.Errorf(`NumSet(%q) index %d: reversed range`, s, i)
} else if i != n-1 {
t.Errorf(`NumSet(%q) index %d: "n:*" not at the end`, s, i)
}
}
}
}
func TestNumSetInfo(t *testing.T) {
tests := []struct {
s string
q uint32
contains bool
}{
{"", 0, false},
{"", 1, false},
{"", 2, false},
{"", 3, false},
{"", max, false},
{"2", 0, false},
{"2", 1, false},
{"2", 2, true},
{"2", 3, false},
{"2", max, false},
{"*", 0, false}, // Contains("*") is always false, use Dynamic() instead
{"*", 1, false},
{"*", 2, false},
{"*", 3, false},
{"*", max, false},
{"1:*", 0, false},
{"1:*", 1, true},
{"1:*", max, true},
{"2:4", 0, false},
{"2:4", 1, false},
{"2:4", 2, true},
{"2:4", 3, true},
{"2:4", 4, true},
{"2:4", 5, false},
{"2:4", max, false},
{"2,4", 0, false},
{"2,4", 1, false},
{"2,4", 2, true},
{"2,4", 3, false},
{"2,4", 4, true},
{"2,4", 5, false},
{"2,4", max, false},
{"2:4,6", 0, false},
{"2:4,6", 1, false},
{"2:4,6", 2, true},
{"2:4,6", 3, true},
{"2:4,6", 4, true},
{"2:4,6", 5, false},
{"2:4,6", 6, true},
{"2:4,6", 7, false},
{"2,4:6", 0, false},
{"2,4:6", 1, false},
{"2,4:6", 2, true},
{"2,4:6", 3, false},
{"2,4:6", 4, true},
{"2,4:6", 5, true},
{"2,4:6", 6, true},
{"2,4:6", 7, false},
{"2,4,6", 0, false},
{"2,4,6", 1, false},
{"2,4,6", 2, true},
{"2,4,6", 3, false},
{"2,4,6", 4, true},
{"2,4,6", 5, false},
{"2,4,6", 6, true},
{"2,4,6", 7, false},
{"1,3:5,7,9:*", 0, false},
{"1,3:5,7,9:*", 1, true},
{"1,3:5,7,9:*", 2, false},
{"1,3:5,7,9:*", 3, true},
{"1,3:5,7,9:*", 4, true},
{"1,3:5,7,9:*", 5, true},
{"1,3:5,7,9:*", 6, false},
{"1,3:5,7,9:*", 7, true},
{"1,3:5,7,9:*", 8, false},
{"1,3:5,7,9:*", 9, true},
{"1,3:5,7,9:*", 10, true},
{"1,3:5,7,9:*", max, true},
{"1,3:5,7,9,42", 0, false},
{"1,3:5,7,9,42", 1, true},
{"1,3:5,7,9,42", 2, false},
{"1,3:5,7,9,42", 3, true},
{"1,3:5,7,9,42", 4, true},
{"1,3:5,7,9,42", 5, true},
{"1,3:5,7,9,42", 6, false},
{"1,3:5,7,9,42", 7, true},
{"1,3:5,7,9,42", 8, false},
{"1,3:5,7,9,42", 9, true},
{"1,3:5,7,9,42", 10, false},
{"1,3:5,7,9,42", 41, false},
{"1,3:5,7,9,42", 42, true},
{"1,3:5,7,9,42", 43, false},
{"1,3:5,7,9,42", max, false},
{"1,3:5,7,9,42,*", 0, false},
{"1,3:5,7,9,42,*", 1, true},
{"1,3:5,7,9,42,*", 2, false},
{"1,3:5,7,9,42,*", 3, true},
{"1,3:5,7,9,42,*", 4, true},
{"1,3:5,7,9,42,*", 5, true},
{"1,3:5,7,9,42,*", 6, false},
{"1,3:5,7,9,42,*", 7, true},
{"1,3:5,7,9,42,*", 8, false},
{"1,3:5,7,9,42,*", 9, true},
{"1,3:5,7,9,42,*", 10, false},
{"1,3:5,7,9,42,*", 41, false},
{"1,3:5,7,9,42,*", 42, true},
{"1,3:5,7,9,42,*", 43, false},
{"1,3:5,7,9,42,*", max, false},
{"1,3:5,7,9,42,60:70,100:*", 0, false},
{"1,3:5,7,9,42,60:70,100:*", 1, true},
{"1,3:5,7,9,42,60:70,100:*", 2, false},
{"1,3:5,7,9,42,60:70,100:*", 3, true},
{"1,3:5,7,9,42,60:70,100:*", 4, true},
{"1,3:5,7,9,42,60:70,100:*", 5, true},
{"1,3:5,7,9,42,60:70,100:*", 6, false},
{"1,3:5,7,9,42,60:70,100:*", 7, true},
{"1,3:5,7,9,42,60:70,100:*", 8, false},
{"1,3:5,7,9,42,60:70,100:*", 9, true},
{"1,3:5,7,9,42,60:70,100:*", 10, false},
{"1,3:5,7,9,42,60:70,100:*", 41, false},
{"1,3:5,7,9,42,60:70,100:*", 42, true},
{"1,3:5,7,9,42,60:70,100:*", 43, false},
{"1,3:5,7,9,42,60:70,100:*", 59, false},
{"1,3:5,7,9,42,60:70,100:*", 60, true},
{"1,3:5,7,9,42,60:70,100:*", 65, true},
{"1,3:5,7,9,42,60:70,100:*", 70, true},
{"1,3:5,7,9,42,60:70,100:*", 71, false},
{"1,3:5,7,9,42,60:70,100:*", 99, false},
{"1,3:5,7,9,42,60:70,100:*", 100, true},
{"1,3:5,7,9,42,60:70,100:*", 1000, true},
{"1,3:5,7,9,42,60:70,100:*", max, true},
}
for _, test := range tests {
s, _ := ParseSet(test.s)
checkNumSet(s, t)
if s.Contains(test.q) != test.contains {
t.Errorf("%q.Contains(%v) expected %v", test.s, test.q, test.contains)
}
if str := s.String(); str != test.s {
t.Errorf("%q.String() expected %q; got %q", test.s, test.s, str)
}
testEmpty := len(test.s) == 0
if (len(s) == 0) != testEmpty {
t.Errorf("%q.Empty() expected %v", test.s, testEmpty)
}
testDynamic := !testEmpty && test.s[len(test.s)-1] == '*'
if s.Dynamic() != testDynamic {
t.Errorf("%q.Dynamic() expected %v", test.s, testDynamic)
}
}
}
func TestParseNumSet(t *testing.T) {
tests := []struct {
in string
out string
}{
{"1,1", "1"},
{"1,2", "1:2"},
{"1,3", "1,3"},
{"1,*", "1,*"},
{"1,1,1", "1"},
{"1,1,2", "1:2"},
{"1,1:2", "1:2"},
{"1,1,3", "1,3"},
{"1,1:3", "1:3"},
{"1,2,2", "1:2"},
{"1,2,3", "1:3"},
{"1,2:3", "1:3"},
{"1,2,4", "1:2,4"},
{"1,3,3", "1,3"},
{"1,3,4", "1,3:4"},
{"1,3:4", "1,3:4"},
{"1,3,5", "1,3,5"},
{"1,3:5", "1,3:5"},
{"1:3,5", "1:3,5"},
{"1:5,3", "1:5"},
{"1,2,3,4", "1:4"},
{"1,2,4,5", "1:2,4:5"},
{"1,2,4:5", "1:2,4:5"},
{"1:2,4:5", "1:2,4:5"},
{"1,2,3,4,5", "1:5"},
{"1,2:3,4:5", "1:5"},
{"1,2,4,5,7,9", "1:2,4:5,7,9"},
{"1,2,4,5,7:9", "1:2,4:5,7:9"},
{"1:2,4:5,7:9", "1:2,4:5,7:9"},
{"1,2,4,5,7,8,9", "1:2,4:5,7:9"},
{"1:2,4:5,7,8,9", "1:2,4:5,7:9"},
{"3,5:10,15:20", "3,5:10,15:20"},
{"4,5:10,15:20", "4:10,15:20"},
{"5,5:10,15:20", "5:10,15:20"},
{"7,5:10,15:20", "5:10,15:20"},
{"10,5:10,15:20", "5:10,15:20"},
{"11,5:10,15:20", "5:11,15:20"},
{"12,5:10,15:20", "5:10,12,15:20"},
{"14,5:10,15:20", "5:10,14:20"},
{"17,5:10,15:20", "5:10,15:20"},
{"21,5:10,15:20", "5:10,15:21"},
{"22,5:10,15:20", "5:10,15:20,22"},
{"*,5:10,15:20", "5:10,15:20,*"},
{"1:3,5:10,15:20", "1:3,5:10,15:20"},
{"1:4,5:10,15:20", "1:10,15:20"},
{"1:8,5:10,15:20", "1:10,15:20"},
{"1:13,5:10,15:20", "1:13,15:20"},
{"1:14,5:10,15:20", "1:20"},
{"7:17,5:10,15:20", "5:20"},
{"11:14,5:10,15:20", "5:20"},
{"12,13,5:10,15:20", "5:10,12:13,15:20"},
{"12:13,5:10,15:20", "5:10,12:13,15:20"},
{"12:14,5:10,15:20", "5:10,12:20"},
{"11:13,5:10,15:20", "5:13,15:20"},
{"11,12,13,14,5:10,15:20", "5:20"},
{"1:*,5:10,15:20", "1:*"},
{"4:*,5:10,15:20", "4:*"},
{"6:*,5:10,15:20", "5:*"},
{"12:*,5:10,15:20", "5:10,12:*"},
{"19:*,5:10,15:20", "5:10,15:*"},
{"5:8,6,7:10,15,16,17,18:20,19,21:*", "5:10,15:*"},
{"4:13,1,5,10,15,20", "1,4:13,15,20"},
{"4:14,1,5,10,15,20", "1,4:15,20"},
{"4:15,1,5,10,15,20", "1,4:15,20"},
{"4:16,1,5,10,15,20", "1,4:16,20"},
{"4:17,1,5,10,15,20", "1,4:17,20"},
{"4:18,1,5,10,15,20", "1,4:18,20"},
{"4:19,1,5,10,15,20", "1,4:20"},
{"4:20,1,5,10,15,20", "1,4:20"},
{"4:21,1,5,10,15,20", "1,4:21"},
{"4:*,1,5,10,15,20", "1,4:*"},
{"1,3,5,7,9,11,13,15,17,19", "1,3,5,7,9,11,13,15,17,19"},
{"1,3,5,7,9,11:13,15,17,19", "1,3,5,7,9,11:13,15,17,19"},
{"1,3,5,7,9:11,13:15,17,19", "1,3,5,7,9:11,13:15,17,19"},
{"1,3,5,7:9,11:13,15:17,19", "1,3,5,7:9,11:13,15:17,19"},
{"1,3,5,7,9,11,13,15,17,19,*", "1,3,5,7,9,11,13,15,17,19,*"},
{"1,3,5,7,9,11,13,15,17,19:*", "1,3,5,7,9,11,13,15,17,19:*"},
{"1:20,3,5,7,9,11,13,15,17,19,*", "1:20,*"},
{"1:20,3,5,7,9,11,13,15,17,19:*", "1:*"},
{"4294967295,*", "4294967295,*"},
{"1,4294967295,*", "1,4294967295,*"},
{"1:4294967295,*", "1:4294967295,*"},
{"1,4294967295:*", "1,4294967295:*"},
{"1:*,4294967295", "1:*"},
{"1:*,4294967295:*", "1:*"},
{"1:4294967295,4294967295:*", "1:*"},
}
prng := rand.New(rand.NewSource(19860201))
done := make(map[string]bool)
permute := func(in string) string {
v := strings.Split(in, ",")
r := make([]string, len(v))
// Try to find a permutation that hasn't been checked already
for i := 0; i < 50; i++ {
for i, j := range prng.Perm(len(v)) {
r[i] = v[j]
}
if s := strings.Join(r, ","); !done[s] {
done[s] = true
return s
}
}
return ""
}
for _, test := range tests {
for i := 0; i < 100 && test.in != ""; i++ {
s, err := ParseSet(test.in)
if err != nil {
t.Errorf("Add(%q) unexpected error; %v", test.in, err)
i = 100
}
checkNumSet(s, t)
if out := s.String(); out != test.out {
t.Errorf("%q.String() expected %q; got %q", test.in, test.out, out)
i = 100
}
test.in = permute(test.in)
}
}
}
func TestNumSetAddNumRangeSet(t *testing.T) {
type num []uint32
tests := []struct {
num num
rng Range
set string
out string
}{
{num{5}, Range{1, 3}, "1:2,5,7:13,15,17:*", "1:3,5,7:13,15,17:*"},
{num{5}, Range{3, 1}, "2:3,7:13,15,17:*", "1:3,5,7:13,15,17:*"},
{num{15}, Range{17, 0}, "1:3,5,7:13", "1:3,5,7:13,15,17:*"},
{num{15}, Range{0, 17}, "1:3,5,7:13", "1:3,5,7:13,15,17:*"},
{num{1, 3, 5, 7, 9, 11, 0}, Range{8, 13}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
{num{5, 1, 7, 3, 9, 0, 11}, Range{8, 13}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
{num{5, 1, 7, 3, 9, 0, 11}, Range{13, 8}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
}
for _, test := range tests {
other, _ := ParseSet(test.set)
var s Set
s.AddNum(test.num...)
checkNumSet(s, t)
s.AddRange(test.rng.Start, test.rng.Stop)
checkNumSet(s, t)
s.AddSet(other)
checkNumSet(s, t)
if out := s.String(); out != test.out {
t.Errorf("(%v + %v + %q).String() expected %q; got %q", test.num, test.rng, test.set, test.out, out)
}
}
}