olinox пре 8 година
родитељ
комит
ce7f521e54
2 измењених фајлова са 139 додато и 12 уклоњено
  1. 18 12
      dice.py
  2. 121 0
      test.py

+ 18 - 12
dice.py

@@ -21,7 +21,7 @@ def roll(pattern_string):
 def rolldice(faces, amount=1):
     return Dice(faces, amount).roll()
 
-_ALLOWED = {'abs': abs}
+_ALLOWED = {'abs': abs, 'max': max, 'min': min}
 
 def _secured_eval(raw):
     """ securely evaluate the incoming raw string by avoiding the use of any non-allowed function """
@@ -51,7 +51,7 @@ class Dice():
             if not int(sides) >= 1:
                 raise ValueError()
         except (TypeError, ValueError):
-            raise TypeError("Invalid value for sides (given: '{}')".format(sides))
+            raise ValueError("Invalid value for sides (given: '{}')".format(sides))
         self._sides = sides
 
     @property
@@ -70,6 +70,9 @@ class Dice():
     def __repr__(self):
         return "<Dice; sides={}; amount={}>".format(self.sides, self.amount)
 
+    def __eq__(self, d):
+        return self.sides == d.sides and self.amount == d.amount
+
     def roll(self):
         """ Role the dice and return a Score object """
         return Score([random.randint(1, self._sides) for _ in range(self._amount)])
@@ -113,7 +116,7 @@ class Score(int):
         return self._detail
 
     def __repr__(self):
-        return "<Score; score={}; detail={}>".format(self.detail, int(self))
+        return "<Score; score={}; detail={}>".format(int(self), self.detail)
 
     def __contains__(self, value):
         return self.detail.__contains__(value)
@@ -121,11 +124,12 @@ class Score(int):
     def __iter__(self):
         return self.detail.__iter__()
 
-
 class Pattern():
     def __init__(self, instr):
+        if not instr:
+            raise ValueError("Invalid value for 'instr' ('{}')".format(instr))
         self.instr = Pattern._normalize(instr)
-        self.dices = {}
+        self.dices = []
         self.format_string = ""
 
     @staticmethod
@@ -137,21 +141,21 @@ class Pattern():
 
         def _submatch(match):
             dice = Dice.parse(match.group(0))
-            name = "d{}".format(len(self.dices) + 1)
-            self.dices[name] = dice
-            return "{{{}}}".format(name)
+            index = len(self.dices)
+            self.dices.append(dice)
+            return "{{{}}}".format(index)
 
         self.format_string = re.sub('\d+d\d+', _submatch, self.instr)
 
     def roll(self):
         if not self.format_string:
             self.compile()
-        scores = {name: dice.roll() for name, dice in self.dices.items()}
+        scores = [dice.roll() for dice in self.dices]
         return PatternScore(self.format_string, scores)
 
 class PatternScore(int):
     def __new__(cls, eval_string, scores):
-        ps = super(PatternScore, cls).__new__(cls, _secured_eval(eval_string.format(**scores)))
+        ps = super(PatternScore, cls).__new__(cls, _secured_eval(eval_string.format(*scores)))
 
         ps._eval_string = eval_string
         ps._scores = scores
@@ -159,10 +163,12 @@ class PatternScore(int):
         return ps
 
     def format(self):
-        return self._eval_string.format(**{name: str(list(score)) for name, score in self._scores.items()})
+        return self._eval_string.format(*[str(list(score)) for score in self._scores])
 
     def score(self, i):
-        return self.scores[i]
+        return self._scores[i]
 
     def scores(self):
         return self._scores
+
+

+ 121 - 0
test.py

@@ -0,0 +1,121 @@
+'''
+Created on 18 nov. 2016
+
+@author: olinox
+'''
+import unittest
+
+import dice
+
+
+class Test(unittest.TestCase):
+    """unitests for DiceRollParser"""
+
+    def test_secured_eval(self):
+        # refused
+        self.assertRaises(TypeError, dice._secured_eval, "open('foo', 'r')")
+        # accepted
+        dice._secured_eval("1 + max([1,2,3])")
+        dice._secured_eval("1 + min([1,2,3])")
+
+    def test_patterns_validation(self):
+        """check the with behaviour with valid / invalid patterns"""
+        # check valid expressions
+        dice.roll("3")
+        dice.roll("0d6")
+        dice.roll("1d6")
+        dice.roll("1d66")
+        dice.roll("1d4+2")
+        dice.roll("1d4-2")
+        dice.roll("1d4 - 2 ")
+        dice.roll("6d102+123")
+        dice.roll("6d102+123+8d6")
+        dice.roll("6d102+123+8d6+2+1+1-8-6d4+8-2d101+100d2")
+        dice.roll("32+3-0-2")
+        dice.roll("1d1-3")
+        dice.roll("1000d1000")
+        dice.roll("10 d 10")
+        dice.roll("10*1d10/2")
+        dice.roll("1d20**2")
+        dice.roll("abs(1d6-1d10)")
+        dice.roll("max(1d6,2d4)")
+        dice.roll("min(1d6,2d4)")
+
+        # test invalid expressions
+        self.assertRaises(SyntaxError, dice.roll, "1d-8")
+        self.assertRaises(SyntaxError, dice.roll, "1d")
+        self.assertRaises(ValueError, dice.roll, "")
+        self.assertRaises(ValueError, dice.roll, "1d0")
+        self.assertRaises(TypeError, dice.roll, "d6")
+        self.assertRaises(TypeError, dice.roll, "abc")
+        self.assertRaises(TypeError, dice.roll, "1d2,3")
+
+    def test_dice_object(self):
+
+        d = dice.Dice(6, 6)
+        self.assertEqual(d.sides, 6)
+        self.assertEqual(d.amount, 6)
+        self.assertEqual(d.__repr__(), "<Dice; sides=6; amount=6>")
+
+        self.assertRaises(ValueError, setattr, d, "sides", -1)
+        self.assertRaises(ValueError, setattr, d, "sides", "a")
+        self.assertRaises(ValueError, setattr, d, "sides", None)
+        self.assertRaises(ValueError, setattr, d, "amount", -1)
+        self.assertRaises(ValueError, setattr, d, "amount", "a")
+        self.assertRaises(ValueError, setattr, d, "amount", None)
+
+        self.assertEqual(dice.Dice(1, 6).roll(), 6)
+
+        self.assertEqual(dice.Dice.parse("6d1").roll(), 6)
+
+    def test_score_object(self):
+
+        s = dice.Score([1, 2, 3])
+
+        self.assertEqual(s, 6)
+        self.assertEqual(str(s), "6")
+        self.assertEqual(list(s), [1, 2, 3])
+        self.assertEqual(s.detail, [1, 2, 3])
+        self.assertTrue(1 in s)
+        self.assertEqual(s.__repr__(), "<Score; score=6; detail=[1, 2, 3]>")
+
+
+    def test_pattern_object(self):
+
+        p = dice.Pattern("6d1+6")
+
+        self.assertEqual(p._normalize("1 D 6"), "1d6")
+
+        p.compile()
+        self.assertEqual(p.format_string, "{0}+6")
+        self.assertEqual(p.dices, [dice.Dice(1, 6)])
+
+        self.assertEqual(p.roll(), 12)
+
+    def test_patternscore_objet(self):
+        ps = dice.PatternScore("{0}+6", [dice.Score([1, 1, 1, 1, 1, 1])])
+
+        self.assertEqual(ps, 12)
+        self.assertEqual(ps.score(0), 6)
+        self.assertEqual(ps.scores(), [6])
+        self.assertEqual(ps.format(), "[1, 1, 1, 1, 1, 1]+6")
+
+    def test_compile(self):
+        p1 = dice.compile("6d1+6")
+        p2 = dice.Pattern("6d1+6")
+        p2.compile()
+
+        self.assertEqual(p1.format_string, p2.format_string)
+        self.assertEqual(p1.dices, p2.dices)
+
+    def test_roll(self):
+        ps1 = dice.roll("6d1+6")
+        ps2 = dice.PatternScore("{0}+6", [dice.Score([1, 1, 1, 1, 1, 1])])
+
+        self.assertEqual(ps1, ps2)
+
+    def test_rolldice(self):
+        self.assertEqual(dice.rolldice(1, 6), dice.Dice(1, 6).roll())
+
+if __name__ == "__main__":
+    unittest.main()