Repository: joke2k/faker

Issue:
Method randomize_nb_elements can generate 0
Hi everyone,

Just to let you know that the method **_randomize_nb_elements_** of the BaseProvider is implemented in a way that it can returns 0 (e.g. if the randint is below 100), but this method is called from the **_sentence_** method of the LoremProvider which will break if the randomized number of elements is 0. Because it will try to generate a sentence of 0 word and access the first element.

Where it is used:

    if variable_nb_words:
        nb_words = self.randomize_nb_elements(nb_words)

Where it breaks:

    words = self.words(nb=nb_words, ext_word_list=ext_word_list)
    words[0] = words[0].title()

PS: Faker version => 0.8.0


Discussion:

Resolving patch:
diff --git a/faker/providers/__init__.py b/faker/providers/__init__.py
--- a/faker/providers/__init__.py
+++ b/faker/providers/__init__.py
@@ -131,7 +131,7 @@ def random_sample_unique(self, elements=('a', 'b', 'c'), length=None):
             sample.add(self.random_element(elements))
         return sample
 
-    def randomize_nb_elements(self, number=10, le=False, ge=False):
+    def randomize_nb_elements(self, number=10, le=False, ge=False, min=None, max=None):
         """
         Returns a random value near number.
 
@@ -144,7 +144,12 @@ def randomize_nb_elements(self, number=10, le=False, ge=False):
             return number
         _min = 100 if ge else 60
         _max = 100 if le else 140
-        return int(number * self.generator.random.randint(_min, _max) / 100) + 1
+        nb = int(number * self.generator.random.randint(_min, _max) / 100)
+        if min is not None and nb < min:
+            nb = min
+        if max is not None and nb > min:
+            nb = max
+        return nb
 
     def numerify(self, text='###'):
         """
diff --git a/faker/providers/lorem/__init__.py b/faker/providers/lorem/__init__.py
--- a/faker/providers/lorem/__init__.py
+++ b/faker/providers/lorem/__init__.py
@@ -70,7 +70,7 @@ def sentence(self, nb_words=6, variable_nb_words=True, ext_word_list=None):
             return ''
 
         if variable_nb_words:
-            nb_words = self.randomize_nb_elements(nb_words)
+            nb_words = self.randomize_nb_elements(nb_words, min=1)
 
         words = self.words(nb=nb_words, ext_word_list=ext_word_list)
         words[0] = words[0].title()
@@ -110,7 +110,7 @@ def paragraph(self, nb_sentences=3, variable_nb_sentences=True, ext_word_list=No
             return ''
 
         if variable_nb_sentences:
-            nb_sentences = self.randomize_nb_elements(nb_sentences)
+            nb_sentences = self.randomize_nb_elements(nb_sentences, min=1)
 
         para = self.word_connector.join(self.sentences(
             nb_sentences, ext_word_list=ext_word_list
diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py
--- a/faker/providers/python/__init__.py
+++ b/faker/providers/python/__init__.py
@@ -91,7 +91,7 @@ def _pyiterable(self, nb_elements=10, variable_nb_elements=True, *value_types):
             value_types = ['str', 'str', 'str', 'str', 'float', 'int', 'int', 'decimal', 'date_time', 'uri', 'email']
 
         if variable_nb_elements:
-            nb_elements = self.randomize_nb_elements(nb_elements)
+            nb_elements = self.randomize_nb_elements(nb_elements, min=1)
 
         for f in range(nb_elements):
             yield self._random_type(value_types)
@@ -105,7 +105,7 @@ def pydict(self, nb_elements=10, variable_nb_elements=True, *value_types):
         :value_types: type of dictionary values
         """
         if variable_nb_elements:
-            nb_elements = self.randomize_nb_elements(nb_elements)
+            nb_elements = self.randomize_nb_elements(nb_elements, min=1)
 
         return dict(zip(
             self.generator.words(nb_elements),


Test patch:
diff --git a/tests/providers/__init__.py b/tests/providers/__init__.py
--- a/tests/providers/__init__.py
+++ b/tests/providers/__init__.py
@@ -0,0 +1,51 @@
+import unittest
+from faker import Factory
+from faker.providers import BaseProvider
+
+
+class BaseProviderTestCase(unittest.TestCase):
+    def setUp(self):
+        self.factory = Factory.create('en_US')
+        self.factory.seed(0)
+        self.provider = BaseProvider(generator=self.factory)
+
+    def test_random_digit_or_empty(self):
+        ret = self.provider.random_digit_or_empty()
+        assert isinstance(ret, int)
+        assert ret >= 0 and ret <= 9
+
+        self.factory.seed(1)
+        assert self.provider.random_digit_or_empty() == ''
+
+    def test_random_digit_not_null_or_empty(self):
+        ret = self.provider.random_digit_not_null_or_empty()
+        assert isinstance(ret, int)
+        assert ret >= 0 and ret <= 9
+
+        self.factory.seed(1)
+        assert self.provider.random_digit_not_null_or_empty() == ''
+
+    def test_randomize_nb_elements(self):
+        assert self.provider.randomize_nb_elements(number=1, le=True, ge=True) == 1
+
+        assert self.provider.randomize_nb_elements(le=True, ge=True) == 10
+
+        number = 9999
+        random_times = 100
+        lower_bound = int(number * 0.6)
+        upper_bound = int(number * 1.4)
+
+        for _ in range(random_times):
+            res = self.provider.randomize_nb_elements(number=number, le=True)
+            assert res >= lower_bound
+            assert res <= number, "'{}' is not <= than '{}'".format(res, number)
+
+        for _ in range(random_times):
+            res = self.provider.randomize_nb_elements(number=number, ge=True)
+            assert res >= number
+            assert res <= upper_bound
+
+        for _ in range(random_times):
+            res = self.provider.randomize_nb_elements(number=number)
+            assert res >= lower_bound
+            assert res <= upper_bound