Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions lib/ClusterShell/RangeSet.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,30 @@ def _parse(self, pattern):
raise RangeSetParseError(subrange,
"cannot convert string to integer")

begin_sign = end_sign = 1 # sign "scale factor"

if baserange.find('-') < 0:
if step != 1:
raise RangeSetParseError(subrange, "invalid step usage")
begin = end = baserange
else:
# ignore whitespaces in a range
begin, end = (n.strip() for n in baserange.split('-', 1))
try:
begin, end = (n.strip() for n in baserange.split('-'))
if not begin: # single negative number "-5"
begin = end
begin_sign = end_sign = -1
except ValueError:
try:
# -0-3
_, begin, end = (n.strip()
for n in baserange.split('-'))
begin_sign = -1
except ValueError:
# -8--4
_, begin, _, end = (n.strip()
for n in baserange.split('-'))
begin_sign = end_sign = -1

# compute padding and return node range info tuple
try:
Expand All @@ -169,6 +186,7 @@ def _parse(self, pattern):
if (pad > 0 or endpad > 0) and len(begin) != len(end):
raise RangeSetParseError(subrange,
"padding length mismatch")

stop = int(ends)
except ValueError:
if len(subrange) == 0:
Expand All @@ -178,10 +196,14 @@ def _parse(self, pattern):
raise RangeSetParseError(subrange, msg)

# check preconditions
if stop > 1e100 or start > stop or step < 1:
if pad > 0 and begin_sign < 0:
errmsg = "padding not supported in negative ranges"
raise RangeSetParseError(subrange, errmsg)

if stop > 1e100 or start * begin_sign > stop * end_sign or step < 1:
raise RangeSetParseError(subrange, "invalid values in range")

self.add_range(start, stop + 1, step, pad)
self.add_range(start * begin_sign, stop * end_sign + 1, step, pad)

@classmethod
def fromlist(cls, rnglist, autostep=None):
Expand Down Expand Up @@ -261,8 +283,10 @@ def dim(self):

def _sorted(self):
"""Get sorted list from inner set."""
# for mixed padding support, sort by both string length and index
return sorted(set.__iter__(self), key=lambda x: (len(x), x))
# For mixed padding support, sort by both string length and index
return sorted(set.__iter__(self),
key=lambda x: (-len(x), int(x)) if x.startswith('-') \
else (len(x), x))

def __iter__(self):
"""Iterate over each element in RangeSet, currently as integers, with
Expand Down Expand Up @@ -456,7 +480,7 @@ def _slices_padding(self, autostep=AUTOSTEP_DISABLED):
if stepped or cur_step == 1:
yield slice(cur_start, last_idx + 1, cur_step), cur_pad if cur_padded else 0
else:
for j in range(cur_start, idx + 1, cur_step):
for j in range(cur_start, last_idx + 1, cur_step):
yield slice(j, j + 1, 1), cur_pad if cur_padded else 0

def _contiguous_slices(self):
Expand Down
32 changes: 29 additions & 3 deletions tests/NodeSetTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def testBadRangeUsages(self):
self._assertNS("nova[2-5/a]", NodeSetParseRangeError)
self._assertNS("nova[3/2]", NodeSetParseRangeError)
self._assertNS("nova[3-/2]", NodeSetParseRangeError)
self._assertNS("nova[-3/2]", NodeSetParseRangeError)
self._assertNS("nova[-/2]", NodeSetParseRangeError)
self._assertNS("nova[4-a/2]", NodeSetParseRangeError)
self._assertNS("nova[4-3/2]", NodeSetParseRangeError)
Expand All @@ -161,7 +160,6 @@ def testBadRangeUsages(self):
self._assertNS("nova[2-5/a]p0", NodeSetParseRangeError)
self._assertNS("nova[3/2]p0", NodeSetParseRangeError)
self._assertNS("nova[3-/2]p0", NodeSetParseRangeError)
self._assertNS("nova[-3/2]p0", NodeSetParseRangeError)
self._assertNS("nova[-/2]p0", NodeSetParseRangeError)
self._assertNS("nova[4-a/2]p0", NodeSetParseRangeError)
self._assertNS("nova[4-3/2]p0", NodeSetParseRangeError)
Expand All @@ -177,7 +175,6 @@ def testBadRangeUsages(self):
self._assertNS("x4nova[2-5/a]p0", NodeSetParseRangeError)
self._assertNS("x4nova[3/2]p0", NodeSetParseRangeError)
self._assertNS("x4nova[3-/2]p0", NodeSetParseRangeError)
self._assertNS("x4nova[-3/2]p0", NodeSetParseRangeError)
self._assertNS("x4nova[-/2]p0", NodeSetParseRangeError)
self._assertNS("x4nova[4-a/2]p0", NodeSetParseRangeError)
self._assertNS("x4nova[4-3/2]p0", NodeSetParseRangeError)
Expand Down Expand Up @@ -2858,3 +2855,32 @@ def test_fully_numeric(self):
# trailing 0 along with mixed lengths padding
self._assertNS("[0-10]0", NodeSetParseRangeError)
self._assertNS("0[0-10]0", NodeSetParseRangeError)

def test_negative_ranges(self):
# supported from 1.9.1 (#515)
n1 = NodeSet("n[-5]")
self.assertEqual(str(n1), "n-5")
self.assertEqual(len(n1), 1)
n1 = NodeSet("n[-1-0]")
self.assertEqual(str(n1), "n[-1-0]")
self.assertEqual(len(n1), 2)
n1 = NodeSet("n[-5-5]")
self.assertEqual(str(n1), "n[-5-5]")
self.assertEqual(len(n1), 2*5+1)
n1 = NodeSet("n[-12-12]")
self.assertEqual(str(n1), "n[-12-12]")
self.assertEqual(len(n1), 12*2+1)
n1 = NodeSet("n[-12,-10--9,-5--1,1-5]")
self.assertEqual(str(n1), "n[-12,-10--9,-5--1,1-5]")
self.assertEqual(len(n1), 13)
n1 = NodeSet("n[1-5,-12,-5--1,-10--9]")
self.assertEqual(str(n1), "n[-12,-10--9,-5--1,1-5]")
self.assertEqual(len(n1), 13)
n1 = NodeSet("n[1,-10,2-4,-12,5,-5,-4,-3,-9,-2--1]")
self.assertEqual(str(n1), "n[-12,-10--9,-5--1,1-5]")
self.assertEqual(len(n1), 13)
n1 = NodeSet("n[1,-10,2-4,-12],n[5,-5,-4,-3,-9,-2--1]")
self.assertEqual(str(n1), "n[-12,-10--9,-5--1,1-5]")
self.assertEqual(len(n1), 13)
# padding in negative range is not supported
self._assertNS("n[-001]", NodeSetParseRangeError)
72 changes: 71 additions & 1 deletion tests/RangeSetTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def test_bad_syntax(self):
self.assertRaises(RangeSetParseError, RangeSet, "2-5/a")
self.assertRaises(RangeSetParseError, RangeSet, "3/2")
self.assertRaises(RangeSetParseError, RangeSet, "3-/2")
self.assertRaises(RangeSetParseError, RangeSet, "-3/2")
self.assertRaises(RangeSetParseError, RangeSet, "-/2")
self.assertRaises(RangeSetParseError, RangeSet, "4-a/2")
self.assertRaises(RangeSetParseError, RangeSet, "4-3/2")
Expand Down Expand Up @@ -1263,3 +1262,74 @@ def test_strip_whitespaces(self):
self.assertRaises(RangeSetParseError, RangeSet, "1,5,")
self.assertRaises(RangeSetParseError, RangeSet, "1,5, ")
self.assertRaises(RangeSetParseError, RangeSet, "1,5,, ")

def test_init_ranges(self):
"""test RangeSet initialization with a range"""
r1 = RangeSet(range(5,7))
self.assertEqual(str(r1), "5-6")
self.assertEqual(len(r1), 2)

def test_init_negative_ranges(self):
"""test RangeSet initialization with with negative ranges"""
# negative ranges (GH#515)
r1 = RangeSet(range(-1,1))
self.assertEqual(str(r1), "-1-0")
self.assertEqual(len(r1), 2)

r1 = RangeSet("-1-0")
self.assertEqual(str(r1), "-1-0")
self.assertEqual(len(r1), 2)

r1 = RangeSet(range(-5,7))
self.assertEqual(str(r1), "-5-6")
self.assertEqual(len(r1), 12)

r1 = RangeSet("-5-6")
self.assertEqual(str(r1), "-5-6")
self.assertEqual(len(r1), 12)

r1 = RangeSet(range(-9,-5))
self.assertEqual(str(r1), "-9--6")
self.assertEqual(len(r1), 4)

r1 = RangeSet("-9--6")
self.assertEqual(str(r1), "-9--6")
self.assertEqual(len(r1), 4)

r1 = RangeSet(range(-12,-5))
self.assertEqual(str(r1), "-12--6")
self.assertEqual(len(r1), 7)

r1 = RangeSet(range(-100,-30))
self.assertEqual(str(r1), "-100--31")
self.assertEqual(len(r1), 70)

r1 = RangeSet(range(-10,-2,2))
self.assertEqual(str(r1), "-10,-8,-6,-4")
self.assertEqual(len(r1), 4)

r1 = RangeSet(range(-3, -2))
self.assertEqual(str(r1), "-3")
self.assertEqual(len(r1), 1)

r1 = RangeSet("-3/2")
self.assertEqual(str(r1), "-3")
self.assertEqual(len(r1), 1)

r1 = RangeSet(range(-10,-2,2), autostep=3)
self.assertEqual(str(r1), "-10--4/2")
self.assertEqual(len(r1), 4)

r1 = RangeSet(['-30', '-20', '-28', '-29'])
self.assertEqual(str(r1), "-30--28,-20")
self.assertEqual(len(r1), 4)

r1 = RangeSet(['-31', '-20', '-27', '-29'])
self.assertEqual(str(r1), "-31,-29,-27,-20")
self.assertEqual(len(r1), 4)

# parsing of negative range with padding is not supported
self.assertRaises(RangeSetParseError, RangeSet, "-001")
self.assertRaises(RangeSetParseError, RangeSet, "-009")
self.assertRaises(RangeSetParseError, RangeSet, "-01-00")
self.assertRaises(RangeSetParseError, RangeSet, "-003--001")