Skip to content

Commit 88d88b7

Browse files
fake: Utilize rnd.Pick instead of NumberInRange (#186)
Some minor refactoring Co-authored-by: Kashif Khan <[email protected]>
1 parent 54d2447 commit 88d88b7

File tree

2 files changed

+91
-77
lines changed

2 files changed

+91
-77
lines changed

fake/fake.go

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Package fake provides utilities for generating fake data.
44
package fake
55

66
import (
7-
"crypto/rand"
7+
cryptrnd "crypto/rand"
8+
"encoding/hex"
89
"fmt"
910
"io"
1011
"time"
@@ -32,87 +33,80 @@ var postalCodes = []string{"12345", "23456", "34567", "45678", "56789", "67890",
3233
// RandomUUID generates a fake UUIDv4.
3334
func RandomUUID() (string, error) {
3435
uuid := make([]byte, 16)
35-
_, err := io.ReadFull(rand.Reader, uuid)
36+
_, err := io.ReadFull(cryptrnd.Reader, uuid)
3637
if err != nil {
37-
return "", err
38+
return "", fmt.Errorf("failed to generate random UUID: %w", err)
3839
}
3940

4041
// Set the version to 4
4142
uuid[6] = (uuid[6] & 0x0f) | 0x40
4243
// Set the variant to 2 (RFC 4122)
4344
uuid[8] = (uuid[8] & 0x3f) | 0x80
4445

45-
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", uuid[:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
46+
uuidStr := hex.EncodeToString(uuid)
47+
48+
return uuidStr[0:8] + "-" + uuidStr[8:12] + "-" + uuidStr[12:16] + "-" + uuidStr[16:20] + "-" + uuidStr[20:32], nil
4649
}
4750

4851
// RandomDate generates a random date.
4952
func RandomDate() (time.Time, error) {
50-
start := time.Date(EpochYear, time.Month(EpochMonth), EpochDay, EpochHour, EpochMinute, EpochSecond, EpochNano, time.UTC).Unix()
51-
end := time.Now().Unix()
52-
53-
sec, err := rnd.NumberInRange(start, end)
54-
if err != nil {
55-
return time.Time{}, err
56-
}
53+
start := time.Date(EpochYear, time.Month(EpochMonth), EpochDay, EpochHour, EpochMinute, EpochSecond, EpochNano, time.UTC).UnixNano()
54+
end := time.Now().UnixNano()
5755

58-
nsec, err := rnd.NumberInRange(0, 1e9)
56+
nanos, err := rnd.NumberInRange(start, end)
5957
if err != nil {
60-
return time.Time{}, err
58+
return time.Time{}, fmt.Errorf("failed to generate random time: %w", err)
6159
}
6260

63-
return time.Unix(sec, nsec), nil
61+
return time.Unix(0, nanos), nil
6462
}
6563

6664
// RandomPhoneNumber generates a random phone number.
6765
func RandomPhoneNumber() (string, error) {
6866
areaCode, err := rnd.NumberInRange(100, 999)
6967
if err != nil {
70-
return "", err
68+
return "", fmt.Errorf("failed to generate areaCode: %w", err)
7169
}
7270

7371
firstPart, err := rnd.NumberInRange(100, 999)
7472
if err != nil {
75-
return "", err
73+
return "", fmt.Errorf("failed to generate firstPart: %w", err)
7674
}
7775

7876
secondPart, err := rnd.NumberInRange(1000, 9999)
7977
if err != nil {
80-
return "", err
78+
return "", fmt.Errorf("failed to generate secondPart: %w", err)
8179
}
8280

83-
return fmt.Sprintf("+1 (%d) %d-%d", areaCode, firstPart, secondPart), nil
81+
return fmt.Sprintf("+1 (%03d) %03d-%04d", areaCode, firstPart, secondPart), nil
8482
}
8583

8684
// RandomAddress generates a random address.
8785
func RandomAddress() (string, error) {
88-
streetNumber, err := rnd.NumberInRange(0, MaxStreetNumber)
86+
streetNumber, err := rnd.NumberInRange(1, MaxStreetNumber)
8987
if err != nil {
90-
return "", err
88+
return "", fmt.Errorf("failed to generate street number: %w", err)
9189
}
9290

93-
idx, err := rnd.NumberInRange(0, int64(len(streetNames)-1))
91+
streetName, err := rnd.Pick(streetNames)
9492
if err != nil {
95-
return "", err
93+
return "", fmt.Errorf("failed to pick street name: %w", err)
9694
}
97-
streetName := streetNames[idx]
9895

99-
idx, err = rnd.NumberInRange(0, int64(len(streetNames)-1))
96+
city, err := rnd.Pick(cities)
10097
if err != nil {
101-
return "", err
98+
return "", fmt.Errorf("failed to pick city: %w", err)
10299
}
103-
city := cities[idx]
104100

105-
idx, err = rnd.NumberInRange(0, int64(len(states)-1))
101+
state, err := rnd.Pick(states)
106102
if err != nil {
107-
return "", err
103+
return "", fmt.Errorf("failed to pick state: %w", err)
108104
}
109-
state := states[idx]
110105

111-
idx, err = rnd.NumberInRange(0, int64(len(postalCodes)-1))
106+
postalCode, err := rnd.Pick(postalCodes)
112107
if err != nil {
113-
return "", err
108+
return "", fmt.Errorf("failed to pick postal code: %w", err)
114109
}
115-
postalCode := postalCodes[idx]
116110

117111
return fmt.Sprintf("%d %s, %s, %s %s, USA", streetNumber, streetName, city, state, postalCode), nil
118112
}

fake/fake_test.go

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,98 @@
11
package fake
22

33
import (
4-
"fmt"
54
"regexp"
65
"testing"
6+
"testing/quick"
77
"time"
88
)
99

10-
func TestGenerateUUID(t *testing.T) {
11-
uuidSet := make(map[string]struct{})
12-
for i := range 1000 {
13-
t.Run(fmt.Sprintf("UUIDTest-%d", i), func(t *testing.T) {
14-
uuid, err := RandomUUID()
15-
if err != nil {
16-
t.Fatalf("Expected no error, got %v", err)
17-
}
18-
19-
// Test if UUID is of correct length
20-
if len(uuid) != 36 {
21-
t.Errorf("Expected length 36, got %d", len(uuid))
22-
}
23-
24-
// Test if UUID matches the correct format
25-
isValidUUID := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$`).MatchString
26-
if !isValidUUID(uuid) {
27-
t.Errorf("UUID %s does not match the required format", uuid)
28-
}
29-
30-
// Test for uniqueness
31-
if _, exists := uuidSet[uuid]; exists {
32-
t.Errorf("Duplicate UUID found: %s", uuid)
33-
}
34-
uuidSet[uuid] = struct{}{}
35-
})
10+
func TestRandomUUID(t *testing.T) {
11+
t.Parallel()
12+
13+
re := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$`)
14+
seen := make(map[string]struct{})
15+
16+
prop := func() bool {
17+
u, err := RandomUUID()
18+
if err != nil || len(u) != 36 || !re.MatchString(u) {
19+
return false
20+
}
21+
// simple uniqueness check across trials
22+
if _, dup := seen[u]; dup {
23+
return false
24+
}
25+
seen[u] = struct{}{}
26+
return true
27+
}
28+
29+
if err := quick.Check(prop, &quick.Config{MaxCount: 1_000}); err != nil {
30+
t.Fatalf("RandomUUID property failed: %v", err)
3631
}
3732
}
3833

3934
func TestRandomDate(t *testing.T) {
35+
t.Parallel()
36+
4037
start := time.Date(EpochYear, time.Month(EpochMonth), EpochDay, EpochHour, EpochMinute, EpochSecond, EpochNano, time.UTC)
41-
end := time.Now()
4238

43-
randomDate, err := RandomDate()
44-
if err != nil {
45-
t.Fatalf("Expected no error, but got %v", err)
39+
prop := func() bool {
40+
d, err := RandomDate()
41+
if err != nil {
42+
return false
43+
}
44+
end := time.Now()
45+
return !d.Before(start) && !d.After(end)
4646
}
4747

48-
if randomDate.Before(start) || randomDate.After(end) {
49-
t.Fatalf("Random date %v is outside the expected range [%v, %v]", randomDate, start, end)
48+
if err := quick.Check(prop, &quick.Config{MaxCount: 1_000}); err != nil {
49+
t.Fatalf("RandomDate property failed: %v", err)
5050
}
5151
}
5252

53-
func TestGenerateRandomPhoneNumber(t *testing.T) {
54-
phoneNumber, err := RandomPhoneNumber()
55-
if err != nil {
56-
t.Fatalf("Expected no error, but got %v", err)
53+
// Sanity property: RandomDate never returns the zero time.
54+
func TestRandomDate_NonZero_Property(t *testing.T) {
55+
t.Parallel()
56+
57+
prop := func() bool {
58+
d, err := RandomDate()
59+
return err == nil && !d.IsZero()
5760
}
5861

62+
if err := quick.Check(prop, &quick.Config{MaxCount: 1_000}); err != nil {
63+
t.Fatalf("RandomDate_NonZero property failed: %v", err)
64+
}
65+
}
66+
67+
func TestRandomPhoneNumber(t *testing.T) {
68+
t.Parallel()
69+
5970
re := regexp.MustCompile(`^\+1 \(\d{3}\) \d{3}-\d{4}$`)
6071

61-
if !re.MatchString(phoneNumber) {
62-
t.Errorf("Generated phone number %v does not match the expected format", phoneNumber)
72+
prop := func() bool {
73+
phoneNumber, err := RandomPhoneNumber()
74+
75+
return err == nil && re.MatchString(phoneNumber)
76+
}
77+
78+
if err := quick.Check(prop, &quick.Config{MaxCount: 1_000}); err != nil {
79+
t.Fatalf("RandomPhoneNumber property failed: %v", err)
6380
}
6481
}
6582

6683
func TestRandomAddress(t *testing.T) {
67-
address, err := RandomAddress()
68-
if err != nil {
69-
t.Fatalf("Expected no error, but got %v", err)
70-
}
84+
t.Parallel()
7185

7286
re := regexp.MustCompile(`^\d+ [A-Za-z ]+, [A-Za-z ]+, [A-Z]{2} \d{5}, USA$`)
7387

74-
if !re.MatchString(address) {
75-
t.Errorf("Generated address %v does not match the expected format", address)
88+
prop := func() bool {
89+
address, err := RandomAddress()
90+
91+
return err == nil && re.MatchString(address)
92+
}
93+
94+
if err := quick.Check(prop, &quick.Config{MaxCount: 1_000}); err != nil {
95+
t.Fatalf("RandomAddress property failed: %v", err)
7696
}
7797
}
7898

0 commit comments

Comments
 (0)