Repository: bxcodec/faker
Pull request #130 (2021-02-23 02:34:53)

feat: add min max size for random slice map And allowing one element for Oneof
wish to add a feature that can set minimum random size for slice and map

codecov[bot]: # [Codecov](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=h1) Report
> Merging [#130](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=desc) (981d489) into [master](https://codecov.io/gh/bxcodec/faker/commit/74585ae5f54b45964d75f16d465fdce5d0e563aa?el=desc) (74585ae) will **decrease** coverage by `0.12%`.
> The diff coverage is `80.00%`.

[![Impacted file tree graph](https://codecov.io/gh/bxcodec/faker/pull/130/graphs/tree.svg?width=650&height=150&src=pr&token=cLPGB7MHVc)](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #130      +/-   ##
==========================================
- Coverage   92.17%   92.04%   -0.13%     
==========================================
  Files          11       11              
  Lines        1086     1094       +8     
==========================================
+ Hits         1001     1007       +6     
- Misses         46       47       +1     
- Partials       39       40       +1     
```


| [Impacted Files](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [faker.go](https://codecov.io/gh/bxcodec/faker/pull/130/diff?src=pr&el=tree#diff-ZmFrZXIuZ28=) | `90.05% <80.00%> (-0.23%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=footer). Last update [74585ae...981d489](https://codecov.io/gh/bxcodec/faker/pull/130?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

bxcodec: Hi @autumn31  can you help to check the test, it's failing on the CI

Sorry for the late reply, been busy lately. 🙇🏻 
autumn31: Test fixed. Also I updated the mr title, since I added another feature that `Oneof should accept "one element" cases`.
Wonder how you think about this change?

Patch:
diff --git a/faker.go b/faker.go
index 410d929..b28caa9 100644
--- a/faker.go
+++ b/faker.go
@@ -27,8 +27,10 @@ var (
 	randomStringLen = 25
 	//Sets the boundary for random value generation. Boundaries can not exceed integer(4 byte...)
 	nBoundary = numberBoundary{start: 0, end: 100}
-	//Sets the random size for slices and maps.
-	randomSize = 100
+	//Sets the random max size for slices and maps.
+	randomMaxSize = 100
+	//Sets the random min size for slices and maps.
+	randomMinSize = 0
 	// Sets the single fake data generator to generate unique values
 	generateUniqueValues = false
 	// Sets whether interface{}s should be ignored.
@@ -314,11 +316,26 @@ func SetStringLang(l langRuneBoundary) {
 }
 
 // SetRandomMapAndSliceSize sets the size for maps and slices for random generation.
+// deprecates, currently left for old version usage
 func SetRandomMapAndSliceSize(size int) error {
+	return SetRandomMapAndSliceMaxSize(size)
+}
+
+// SetRandomMapAndSliceMaxSize sets the max size for maps and slices for random generation.
+func SetRandomMapAndSliceMaxSize(size int) error {
 	if size < 1 {
 		return fmt.Errorf(ErrSmallerThanOne, size)
 	}
-	randomSize = size
+	randomMaxSize = size
+	return nil
+}
+
+// SetRandomMapAndSliceMinSize sets the min size for maps and slices for random generation.
+func SetRandomMapAndSliceMinSize(size int) error {
+	if size < 0 {
+		return fmt.Errorf(ErrSmallerThanZero, size)
+	}
+	randomMinSize = size
 	return nil
 }
 
@@ -872,17 +889,10 @@ func extractStringFromTag(tag string) (interface{}, error) {
 		}
 	}
 	if isOneOfTag {
-		items := strings.Split(tag, colon)
-		argsList := items[1:]
-		if len(argsList) != 1 {
-			return nil, fmt.Errorf(ErrUnsupportedTagArguments)
-		}
-		if strings.Contains(argsList[0], ",,") {
-			return nil, fmt.Errorf(ErrDuplicateSeparator)
-		}
-		args := strings.Split(argsList[0], comma)
-		if len(args) < 2 {
-			return nil, fmt.Errorf(ErrNotEnoughTagArguments)
+		var args []string
+		args, err = fetchOneOfArgsFromTag(tag)
+		if err != nil {
+			return nil, err
 		}
 		toRet := args[rand.Intn(len(args))]
 		return strings.TrimSpace(toRet), nil
@@ -923,16 +933,9 @@ func extractNumberFromTag(tag string, t reflect.Type) (interface{}, error) {
 
 	// handling oneof tag
 	if usingOneOfTag {
-		argsList := strings.Split(tag, colon)[1:]
-		if len(argsList) != 1 {
-			return nil, fmt.Errorf(ErrUnsupportedTagArguments)
-		}
-		if strings.Contains(argsList[0], ",,") {
-			return nil, fmt.Errorf(ErrDuplicateSeparator)
-		}
-		args := strings.Split(argsList[0], comma)
-		if len(args) < 2 {
-			return nil, fmt.Errorf(ErrNotEnoughTagArguments)
+		args, err := fetchOneOfArgsFromTag(tag)
+		if err != nil {
+			return nil, err
 		}
 		switch t.Kind() {
 		case reflect.Float64:
@@ -1087,6 +1090,25 @@ func extractNumberFromText(text string) (int, error) {
 	return strconv.Atoi(texts[1])
 }
 
+func fetchOneOfArgsFromTag(tag string) ([]string, error) {
+	items := strings.Split(tag, colon)
+	argsList := items[1:]
+	if len(argsList) != 1 {
+		return nil, fmt.Errorf(ErrUnsupportedTagArguments)
+	}
+	if strings.Contains(argsList[0], ",,") {
+		return nil, fmt.Errorf(ErrDuplicateSeparator)
+	}
+	if argsList[0] == "" {
+		return nil, fmt.Errorf(ErrNotEnoughTagArguments)
+	}
+	args := strings.Split(argsList[0], comma)
+	if len(args) < 1 {
+		return nil, fmt.Errorf(ErrNotEnoughTagArguments)
+	}
+	return args, nil
+}
+
 func randomString(n int, lang *langRuneBoundary) (string, error) {
 	b := make([]rune, 0)
 	set := make(map[rune]struct{})
@@ -1137,7 +1159,11 @@ func randomSliceAndMapSize() int {
 	if testRandZero {
 		return 0
 	}
-	return rand.Intn(randomSize)
+	r := randomMaxSize - randomMinSize
+	if r < 1 {
+		r = 1
+	}
+	return randomMinSize + rand.Intn(r)
 }
 
 func randomElementFromSliceString(s []string) string {


Test patch:
diff --git a/faker_test.go b/faker_test.go
index 0d3c977..ad2d186 100644
--- a/faker_test.go
+++ b/faker_test.go
@@ -1380,7 +1380,7 @@ func TestUniqueFailure(t *testing.T) {
 func TestOneOfTag__GoodInputs(t *testing.T) {
 
 	type CustomOneString struct {
-		PaymentType string `faker:"oneof: credit card, paypal"`
+		PaymentType string `faker:"oneof: credit card"`
 	}
 
 	t.Run("creates one of the desired string values", func(t *testing.T) {
@@ -1389,6 +1389,24 @@ func TestOneOfTag__GoodInputs(t *testing.T) {
 		if err != nil {
 			t.Errorf("expected no error, but got %v", err)
 		}
+		if a.PaymentType != "credit card" {
+			t.Errorf(
+				"expected credit card but got %v",
+				a.PaymentType,
+			)
+		}
+	})
+
+	type CustomTwoString struct {
+		PaymentType string `faker:"oneof: credit card, paypal"`
+	}
+
+	t.Run("creates one of the desired string values", func(t *testing.T) {
+		a := CustomTwoString{}
+		err := FakeData(&a)
+		if err != nil {
+			t.Errorf("expected no error, but got %v", err)
+		}
 		one := a.PaymentType == "credit card"
 		two := a.PaymentType == "paypal"
 
@@ -1450,6 +1468,24 @@ func TestOneOfTag__GoodInputs(t *testing.T) {
 		}
 	})
 
+	type CustomOneofInt struct {
+		Age int `faker:"oneof: 15"`
+	}
+
+	t.Run("errors when tag is not used correctly int only one argument", func(t *testing.T) {
+		a := CustomOneofInt{}
+		err := FakeData(&a)
+		if err != nil {
+			t.Errorf("expected no error, but got %v", err)
+		}
+		if a.Age != 15 {
+			t.Errorf(
+				"expected either %v, but got %v",
+				15, a.Age,
+			)
+		}
+	})
+
 	type CustomFloat1 struct {
 		Price float32 `faker:"oneof: 3.14, 15.92"`
 	}
@@ -1468,6 +1504,36 @@ func TestOneOfTag__GoodInputs(t *testing.T) {
 		}
 	})
 
+	type CustomFloat2 struct {
+		Price float32 `faker:"oneof: 1848205.48483727"`
+	}
+
+	t.Run("errors when tag is not used correctly float32 only one argument", func(t *testing.T) {
+		a := CustomFloat2{}
+		err := FakeData(&a)
+		if err != nil {
+			t.Error("expected no error but got ", err)
+		}
+		if a.Price != float32(1848205.48483727) {
+			t.Errorf("expected either %v but got %v", 1848205.48483727, a.Price)
+		}
+	})
+
+	type CustomFloat3 struct {
+		Price float64 `faker:"oneof: 1848205.48483727"`
+	}
+
+	t.Run("errors when tag is not used correctly float64 only one argument", func(t *testing.T) {
+		a := CustomFloat3{}
+		err := FakeData(&a)
+		if err != nil {
+			t.Error("expected no error but got ", err)
+		}
+		if a.Price != float64(1848205.48483727) {
+			t.Errorf("expected either %v but got %v", 1848205.48483727, a.Price)
+		}
+	})
+
 	type CustomFloat6 struct {
 		Price float64 `faker:"oneof: 34566872.57446732, 969525372.57563314"`
 	}
@@ -1564,23 +1630,6 @@ func TestOneOfTag__BadInputsForFloats(t *testing.T) {
 		}
 	})
 
-	type CustomWrongFloat4 struct {
-		Price float32 `faker:"oneof: 1848205.48483727"`
-	}
-
-	t.Run("errors when tag is not used correctly float32 only one argument", func(t *testing.T) {
-		a := CustomWrongFloat4{}
-		err := FakeData(&a)
-		if err == nil {
-			t.Fatal("expected error, but got no error")
-		}
-		actual := err.Error()
-		expected := ErrNotEnoughTagArguments
-		if actual != expected {
-			t.Errorf("expected %v, but got %v", expected, actual)
-		}
-	})
-
 	type CustomWrongFloat5 struct {
 		Price float32 `faker:"oneof: 15,,16,17"`
 	}
@@ -1649,23 +1698,6 @@ func TestOneOfTag__BadInputsForFloats(t *testing.T) {
 		}
 	})
 
-	type CustomWrongFloat10 struct {
-		Price float64 `faker:"oneof: 1848205.48483727"`
-	}
-
-	t.Run("errors when tag is not used correctly float64 only one argument", func(t *testing.T) {
-		a := CustomWrongFloat10{}
-		err := FakeData(&a)
-		if err == nil {
-			t.Fatal("expected error, but got no error")
-		}
-		actual := err.Error()
-		expected := ErrNotEnoughTagArguments
-		if actual != expected {
-			t.Errorf("expected %v, but got %v", expected, actual)
-		}
-	})
-
 	type CustomWrongFloat11 struct {
 		Price float64 `faker:"oneof: 15,,16,17"`
 	}
@@ -1738,23 +1770,6 @@ func TestOneOfTag__BadInputsForStrings(t *testing.T) {
 		}
 	})
 
-	type CustomWrongString3 struct {
-		PaymentType string `faker:"oneof: credit card"`
-	}
-
-	t.Run("errors when tag is not used correctly string only one argument", func(t *testing.T) {
-		a := CustomWrongString3{}
-		err := FakeData(&a)
-		if err == nil {
-			t.Errorf("expected error, but got no error")
-		}
-		actual := err.Error()
-		expected := ErrNotEnoughTagArguments
-		if actual != expected {
-			t.Errorf("expected %v, but got %v", expected, actual)
-		}
-	})
-
 	type CustomWrongString4 struct {
 		PaymentType string `faker:"oneof: ,,,cc, credit card,,"`
 	}
@@ -1963,23 +1978,6 @@ func TestOneOfTag__BadInputsForInts(t *testing.T) {
 		}
 	})
 
-	type CustomWrongInt4 struct {
-		Age int `faker:"oneof: 15"`
-	}
-
-	t.Run("errors when tag is not used correctly int only one argument", func(t *testing.T) {
-		a := CustomWrongInt4{}
-		err := FakeData(&a)
-		if err == nil {
-			t.Fatal("expected error, but got no error")
-		}
-		actual := err.Error()
-		expected := ErrNotEnoughTagArguments
-		if actual != expected {
-			t.Errorf("expected %v, but got %v", expected, actual)
-		}
-	})
-
 	type CustomWrongInt5 struct {
 		Age int `faker:"oneof: 15,,16,17"`
 	}
@@ -2043,3 +2041,89 @@ func utfLen(value string) int {
 	}
 	return r
 }
+
+func TestRandomMaxMinMapSliceSize(t *testing.T) {
+	type SliceMap struct {
+		Slice []int
+		Map   map[string]int
+	}
+
+	orimax, orimin := randomMaxSize, randomMinSize
+	defer func() {
+		err := SetRandomMapAndSliceMaxSize(orimax)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+	defer func() {
+		err := SetRandomMapAndSliceMinSize(orimin)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for _, c := range []struct {
+		max, min, expect int
+	}{
+		{2, 1, 1}, // [1,2) => always 1
+		{2, 2, 2},
+		{2, 3, 3}, // if min >= max, result will always be min
+	} {
+		err := SetRandomMapAndSliceMaxSize(c.max)
+		if err != nil {
+			t.Fatal(err)
+		}
+		err = SetRandomMapAndSliceMinSize(c.min)
+		if err != nil {
+			t.Fatal(err)
+		}
+		s := SliceMap{}
+		err = FakeData(&s)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if len(s.Map) != c.expect {
+			t.Errorf("map (len:%d) not expect length with test case %+v\n", len(s.Map), c)
+		}
+
+		if len(s.Slice) != c.expect {
+			t.Errorf("slice (len:%d) not expect length with test case %+v\n", len(s.Slice), c)
+		}
+	}
+}
+
+func TestRandomMapSliceSize(t *testing.T) {
+	// test if old func behaves the same
+	type SliceMap struct {
+		Slice []int
+		Map   map[string]int
+	}
+	expect := 5
+	orimax := randomMaxSize
+	defer func() {
+		err := SetRandomMapAndSliceSize(orimax)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+	err := SetRandomMapAndSliceSize(expect)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i := 0; i < 10; i++ {
+		s := SliceMap{}
+		err := FakeData(&s)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if len(s.Map) >= 5 {
+			t.Errorf("map (len:%d) is greater than expected length %d", len(s.Map), expect)
+		}
+
+		if len(s.Slice) >= expect {
+			t.Errorf("slice (len:%d) not expect length with test case %+v\n", len(s.Slice), expect)
+		}
+	}
+}