File: test_determinism.rb

package info (click to toggle)
ruby-faker 3.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,596 kB
  • sloc: ruby: 20,656; sh: 6; makefile: 6
file content (123 lines) | stat: -rw-r--r-- 2,613 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# frozen_string_literal: true

require_relative 'test_helper'
# rubocop:disable Security/Eval,Style/EvalWithLocation
class TestDeterminism < Test::Unit::TestCase
  def setup
    @all_methods = all_methods.freeze
    @first_run = []
  end

  def test_determinism
    Faker::Config.random = Random.new(42)

    @all_methods.each_index do |index|
      store_result @all_methods[index]
    end

    @first_run.freeze

    Faker::Config.random = Random.new(42)

    @all_methods.each_index do |index|
      assert deterministic_random? @first_run[index], @all_methods[index]
    end
  end

  def test_thread_safety
    expected_values = 2.times.map do |index|
      Faker::Config.random = Random.new(index)
      Faker::Number.digit
    end

    threads = expected_values.each_with_index.map do |expected_value, index|
      Thread.new do
        100_000.times.each do
          Faker::Config.random = Random.new(index)
          output = Faker::Number.digit

          assert_equal output, expected_value
        end
      end
    end

    threads.each(&:join)
  end

  private

  def deterministic_random?(first, method_name)
    second = eval(method_name)
    (first == second) || raise(
      "#{method_name} has an entropy leak; use \"Faker::Config.random.rand\" or \"Array#sample(random: Faker::Config.random)\". Method to lookup for: sample, shuffle, rand"
    )
  end

  def store_result(method_name)
    @first_run << eval(method_name)
  rescue StandardError => e
    raise %(#{method_name} raised "#{e}")
  end

  def all_methods
    subclasses.map do |subclass|
      subclass_methods(subclass).flatten
    end.flatten.sort
  end

  def subclasses
    Faker.constants.delete_if do |subclass|
      skipped_classes.include?(subclass)
    end.sort
  end

  def subclass_methods(subclass)
    eval("Faker::#{subclass}.public_methods(false) - Faker::Base.public_methods(false)").sort.map do |method|
      "Faker::#{subclass}.#{method}"
    end.sort
  end

  def skipped_classes
    %i[
      Bank
      Base
      Base58
      Books
      Cat
      Char
      ChileRut
      CLI
      Config
      Creature
      Date
      Deprecator
      Dog
      DragonBall
      Dota
      ElderScrolls
      Fallout
      Games
      GamesHalfLife
      HeroesOfTheStorm
      Internet
      JapaneseMedia
      LeagueOfLegends
      Locations
      Movies
      Myst
      Overwatch
      OnePiece
      Pokemon
      Religion
      Sports
      SwordArtOnline
      TvShows
      Time
      VERSION
      Witcher
      WorldOfWarcraft
      Zelda
    ]
  end
end
# rubocop:enable Security/Eval,Style/EvalWithLocation