• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ahamez / protox / c09d61cc53c3c05b9a3aaf7c915ebb9a99831671

07 Aug 2025 12:55PM UTC coverage: 95.573% (-0.08%) from 95.652%
c09d61cc53c3c05b9a3aaf7c915ebb9a99831671

Pull #248

github

ahamez
Limit map generation for small key domains
Pull Request #248: Replace PropCheck with StreamData

59 of 61 new or added lines in 1 file covered. (96.72%)

1 existing line in 1 file now uncovered.

842 of 881 relevant lines covered (95.57%)

15135.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.22
/test/support/random_init.ex
1
# credo:disable-for-this-file Credo.Check.Refactor.CyclomaticComplexity
2
# credo:disable-for-this-file Credo.Check.Refactor.Nesting
3
defmodule Protox.RandomInit do
4
  @moduledoc false
5

6
  import StreamData
7

8
  alias Protox.{Field, OneOf, Scalar}
9

10
  def generate_msg(mod) do
11
    gen =
25✔
12
      bind(generate_fields(mod), fn fields ->
13
        constant(generate_struct(mod, fields))
25✔
14
      end)
15

16
    gen |> resize(5) |> Enum.at(0)
25✔
17
  end
18

19
  # ------------------------------------------------------------------- #
20

21
  # Recursively generate the sub messages of mod
22
  def generate_struct(mod, nil), do: struct!(mod)
30✔
23

24
  def generate_struct(mod, fields) when is_list(fields) do
25
    sub_msgs =
62,390✔
26
      mod.schema().fields
62,390✔
27
      |> Map.values()
28
      # Get all sub messages
29
      |> Enum.filter(fn %Field{} = field ->
30
        case {field.kind, field.type} do
1,197,106✔
31
          {:map, {_, {:message, _}}} -> true
14,648✔
32
          {_, {:message, _}} -> true
289,702✔
33
          _ -> false
892,756✔
34
        end
35
      end)
36
      # Transform into a map for lookup
37
      |> Enum.reduce(%{}, fn %Field{} = field, acc ->
38
        case field.kind do
304,350✔
39
          %Scalar{} ->
40
            {:message, sub_msg} = field.type
150,802✔
41
            Map.put(acc, field.name, %Scalar{default_value: sub_msg})
150,802✔
42

43
          :unpacked ->
44
            {:message, sub_msg} = field.type
131,576✔
45
            Map.put(acc, field.name, {:repeated, sub_msg})
131,576✔
46

47
          :map ->
48
            {_, {:message, sub_msg}} = field.type
14,648✔
49
            Map.put(acc, field.name, {:map, sub_msg})
14,648✔
50

51
          %OneOf{parent: oneof_name} ->
52
            {:message, sub_msg} = field.type
7,324✔
53

54
            Map.update(
7,324✔
55
              acc,
56
              oneof_name,
57
              # initial insertion
58
              %OneOf{parent: %{field.name => sub_msg}},
7,324✔
59
              fn %OneOf{parent: sub_map} ->
60
                %OneOf{parent: Map.put(sub_map, field.name, sub_msg)}
×
61
              end
62
            )
63
        end
64
      end)
65

66
    new_fields =
62,390✔
67
      Enum.reduce(fields, [], fn {field_name, val}, acc ->
68
        case sub_msgs[field_name] do
165,651✔
69
          # Not a sub message, no transformation and recursion needed
70
          nil ->
124,645✔
71
            [{field_name, val} | acc]
72

73
          %OneOf{parent: sub_map} ->
74
            if val == nil do
723✔
75
              [{field_name, nil} | acc]
76
            else
77
              {sub_field_name, val} = val
662✔
78

79
              case sub_map[sub_field_name] do
662✔
80
                # the enclosing oneof contains one sub message, but sub_map does not
81
                # know about sub non-messages entries, thus we need to add them manually
82
                nil ->
604✔
83
                  [{field_name, {sub_field_name, val}} | acc]
84

85
                sub_msg ->
58✔
86
                  [{field_name, {sub_field_name, generate_struct(sub_msg, val)}} | acc]
87
              end
88
            end
89

90
          %Scalar{default_value: sub_msg} ->
91
            if val == nil do
25,935✔
92
              [{field_name, nil} | acc]
93
            else
94
              [{field_name, generate_struct(sub_msg, val)} | acc]
95
            end
96

97
          {:map, sub_msg} ->
98
            val =
1,446✔
99
              val
100
              |> Map.new(fn {k, msg_val} -> {k, generate_struct(sub_msg, msg_val)} end)
3,521✔
101

102
            [{field_name, val} | acc]
103

104
          {:repeated, sub_msg} ->
105
            val = Enum.map(val, fn msg_val -> generate_struct(sub_msg, msg_val) end)
12,902✔
106
            [{field_name, val} | acc]
107
        end
108
      end)
109

110
    struct!(mod, new_fields)
62,390✔
111
  end
112

113
  # ------------------------------------------------------------------- #
114

115
  @well_known_types [
116
    Google.Protobuf.BoolValue,
117
    Google.Protobuf.BytesValue,
118
    Google.Protobuf.DoubleValue,
119
    Google.Protobuf.Duration,
120
    Google.Protobuf.FieldMask,
121
    Google.Protobuf.FloatValue,
122
    Google.Protobuf.Int32Value,
123
    Google.Protobuf.Int64Value,
124
    Google.Protobuf.ListValue,
125
    Google.Protobuf.NullValue,
126
    Google.Protobuf.StringValue,
127
    Google.Protobuf.Struct,
128
    Google.Protobuf.Timestamp,
129
    Google.Protobuf.UInt32Value,
130
    Google.Protobuf.UInt64Value,
131
    Google.Protobuf.Value
132
  ]
133

134
  def generate_fields(mod, depth \\ 2) do
135
    do_generate([], Map.values(mod.schema().fields), depth)
20,244✔
136
  end
137

138
  defp do_generate(acc, _fields, 0), do: constant(acc)
15,354✔
139
  defp do_generate(acc, [], _depth), do: constant(acc)
38,848✔
140

141
  defp do_generate(acc, [%Field{kind: %OneOf{parent: oneof_name}} | _] = fields, depth) do
142
    {oneof_list, fields} =
687✔
143
      Enum.split_with(fields, fn %Field{} = field ->
144
        case field.kind do
97,446✔
145
          %OneOf{parent: ^oneof_name} -> true
6,862✔
146
          _ -> false
90,584✔
147
        end
148
      end)
149

150
    bind(do_generate_oneof(acc, oneof_name, oneof_list, depth), fn acc ->
687✔
151
      do_generate(acc, fields, depth)
724✔
152
    end)
153
  end
154

155
  defp do_generate(acc, [field | fields], depth) do
156
    bind(get_gen(depth, field.kind, field.type), fn val ->
131,266✔
157
      do_generate([{field.name, val} | acc], fields, depth)
165,187✔
158
    end)
159
  end
160

161
  defp do_generate_oneof(acc, oneof_name, oneof_list, depth) do
162
    generators =
687✔
163
      Enum.map(oneof_list, fn %Field{kind: %OneOf{parent: _}} = field ->
164
        map(get_gen(depth, %Scalar{default_value: :dummy}, field.type), fn val ->
6,862✔
165
          {field.name, val}
663✔
166
        end)
167
      end)
168

169
    bind(one_of([constant(nil) | generators]), fn
687✔
170
      nil ->
171
        constant([{oneof_name, nil} | acc])
61✔
172

173
      {field_name, val} ->
174
        constant([{oneof_name, {field_name, val}} | acc])
663✔
175
    end)
176
  end
177

178
  defp get_gen(_depth, %Scalar{}, {:enum, e}) do
179
    e.constants() |> Map.new() |> Map.values() |> one_of()
5,696✔
180
  end
181

182
  defp get_gen(_depth, %Scalar{}, :bool), do: boolean()
2,865✔
183

184
  defp get_gen(_depth, %Scalar{}, :int32), do: integer()
19,952✔
185
  defp get_gen(_depth, %Scalar{}, :int64), do: integer()
2,177✔
186
  defp get_gen(_depth, %Scalar{}, :sint32), do: integer()
2,177✔
187
  defp get_gen(_depth, %Scalar{}, :sint64), do: integer()
2,177✔
188
  defp get_gen(_depth, %Scalar{}, :sfixed32), do: integer()
2,177✔
189
  defp get_gen(_depth, %Scalar{}, :sfixed64), do: integer()
2,177✔
190
  defp get_gen(_depth, %Scalar{}, :fixed32), do: non_negative_integer()
2,178✔
191
  defp get_gen(_depth, %Scalar{}, :fixed64), do: non_negative_integer()
2,177✔
192

193
  defp get_gen(_depth, %Scalar{}, :uint32), do: non_negative_integer()
2,871✔
194
  defp get_gen(_depth, %Scalar{}, :uint64), do: non_negative_integer()
2,866✔
195

196
  defp get_gen(_depth, %Scalar{}, :float), do: gen_float()
2,140✔
197
  defp get_gen(_depth, %Scalar{}, :double), do: gen_float()
2,140✔
198

199
  defp get_gen(_depth, %Scalar{}, :bytes), do: binary()
3,117✔
200
  defp get_gen(_depth, %Scalar{}, :string), do: string(:printable)
20,070✔
201

202
  defp get_gen(_depth, %Scalar{}, {:message, sub_msg}) when sub_msg in @well_known_types do
203
    constant(nil)
10,029✔
204
  end
205

206
  defp get_gen(depth, %Scalar{}, {:message, sub_msg}) do
207
    one_of([constant(nil), generate_fields(sub_msg, depth - 1)])
16,603✔
208
  end
209

210
  defp get_gen(_depth, :packed, :bool), do: list_of(boolean())
1,440✔
211
  defp get_gen(_depth, :unpacked, :bool), do: list_of(boolean())
731✔
212

213
  defp get_gen(_depth, :packed, :int32), do: list_of(integer())
1,440✔
214
  defp get_gen(_depth, :packed, :int64), do: list_of(integer())
1,439✔
215
  defp get_gen(_depth, :packed, :sint32), do: list_of(integer())
1,441✔
216
  defp get_gen(_depth, :packed, :sint64), do: list_of(integer())
1,439✔
217
  defp get_gen(_depth, :packed, :sfixed32), do: list_of(integer())
1,439✔
218
  defp get_gen(_depth, :packed, :sfixed64), do: list_of(integer())
1,439✔
219
  defp get_gen(_depth, :packed, :fixed32), do: list_of(non_negative_integer())
1,439✔
220
  defp get_gen(_depth, :packed, :fixed64), do: list_of(non_negative_integer())
1,441✔
221
  defp get_gen(_depth, :unpacked, :int32), do: list_of(integer())
730✔
222
  defp get_gen(_depth, :unpacked, :int64), do: list_of(integer())
730✔
223
  defp get_gen(_depth, :unpacked, :sint32), do: list_of(integer())
731✔
224
  defp get_gen(_depth, :unpacked, :sint64), do: list_of(integer())
731✔
225
  defp get_gen(_depth, :unpacked, :sfixed32), do: list_of(integer())
731✔
226
  defp get_gen(_depth, :unpacked, :sfixed64), do: list_of(integer())
730✔
227
  defp get_gen(_depth, :unpacked, :fixed32), do: list_of(non_negative_integer())
731✔
228
  defp get_gen(_depth, :unpacked, :fixed64), do: list_of(non_negative_integer())
731✔
229

230
  defp get_gen(_depth, :packed, :uint32), do: list_of(non_negative_integer())
1,440✔
231
  defp get_gen(_depth, :packed, :uint64), do: list_of(non_negative_integer())
1,441✔
232
  defp get_gen(_depth, :unpacked, :uint32), do: list_of(non_negative_integer())
730✔
233
  defp get_gen(_depth, :unpacked, :uint64), do: list_of(non_negative_integer())
731✔
234

235
  defp get_gen(_depth, :packed, :float), do: list_of(gen_float())
1,356✔
236
  defp get_gen(_depth, :packed, :double), do: list_of(gen_double())
1,441✔
237
  defp get_gen(_depth, :unpacked, :float), do: list_of(gen_float())
731✔
238
  defp get_gen(_depth, :unpacked, :double), do: list_of(gen_double())
730✔
239

240
  defp get_gen(_depth, kind, {:enum, e}) when kind == :packed or kind == :unpacked do
241
    e.constants() |> Map.new() |> Map.values() |> one_of() |> list_of()
2,894✔
242
  end
243

244
  defp get_gen(_depth, :unpacked, :string), do: list_of(string(:printable))
2,170✔
245
  defp get_gen(_depth, :unpacked, :bytes), do: list_of(binary())
724✔
246

247
  defp get_gen(_depth, :unpacked, {:message, sub_msg}) when sub_msg in @well_known_types do
248
    constant([])
10,749✔
249
  end
250

251
  defp get_gen(depth, :unpacked, {:message, sub_msg}) do
252
    list_of(generate_fields(sub_msg, depth - 1))
2,164✔
253
  end
254

255
  defp get_gen(_depth, :map, {_key_ty, {:message, sub_msg}}) when sub_msg in @well_known_types do
NEW
256
    constant(%{})
×
257
  end
258

259
  defp get_gen(depth, :map, {key_ty, {:message, sub_msg}}) do
260
    map_of(
1,447✔
261
      get_gen(depth, %Scalar{default_value: :dummy}, key_ty),
262
      generate_fields(sub_msg, depth - 1),
263
      max_length: map_max_length(key_ty)
264
    )
265
  end
266

267
  defp get_gen(depth, :map, {key_ty, value_ty}) do
268
    map_of(
12,295✔
269
      get_gen(depth, %Scalar{default_value: :dummy}, key_ty),
270
      get_gen(depth, %Scalar{default_value: :dummy}, value_ty),
271
      max_length: map_max_length(key_ty)
272
    )
273
  end
274

275
  defp map_max_length(:bool), do: 2
724✔
276

277
  defp map_max_length({:enum, e}) do
NEW
278
    e.constants() |> Enum.count() |> min(5)
×
279
  end
280

281
  defp map_max_length(_), do: 5
13,018✔
282

283
  # ----------------------
284

285
  defp gen_float() do
286
    one_of([integer(), constant(:nan), constant(:infinity), constant(:"-infinity")])
6,367✔
287
  end
288

289
  defp gen_double() do
290
    one_of([float(), constant(:nan), constant(:infinity), constant(:"-infinity")])
2,171✔
291
  end
292
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc