1# -*- coding: us-ascii -*-
2# frozen_string_literal: false
3require 'test/unit'
4require 'timeout'
5
6module TestStruct
7  def test_struct
8    struct_test = @Struct.new("Test", :foo, :bar)
9    assert_equal(@Struct::Test, struct_test)
10
11    test = struct_test.new(1, 2)
12    assert_equal(1, test.foo)
13    assert_equal(2, test.bar)
14    assert_equal(1, test[0])
15    assert_equal(2, test[1])
16
17    a, b = test.to_a
18    assert_equal(1, a)
19    assert_equal(2, b)
20
21    test[0] = 22
22    assert_equal(22, test.foo)
23
24    test.bar = 47
25    assert_equal(47, test.bar)
26  end
27
28  # [ruby-dev:26247] more than 10 struct members causes segmentation fault
29  def test_morethan10members
30    list = %w( a b c d  e f g h  i j k l  m n o p )
31    until list.empty?
32      c = @Struct.new(* list.map {|ch| ch.intern }).new
33      list.each do |ch|
34        c.__send__(ch)
35      end
36      list.pop
37    end
38  end
39
40  def test_small_structs
41    names = [:a, :b, :c, :d]
42    1.upto(4) {|n|
43      fields = names[0, n]
44      klass = @Struct.new(*fields)
45      o = klass.new(*(0...n).to_a)
46      fields.each_with_index {|name, i|
47        assert_equal(i, o[name])
48      }
49      o = klass.new(*(0...n).to_a.reverse)
50      fields.each_with_index {|name, i|
51        assert_equal(n-i-1, o[name])
52      }
53    }
54  end
55
56  def test_inherit
57    klass = @Struct.new(:a)
58    klass2 = Class.new(klass)
59    o = klass2.new(1)
60    assert_equal(1, o.a)
61  end
62
63  def test_members
64    klass = @Struct.new(:a)
65    o = klass.new(1)
66    assert_equal([:a], klass.members)
67    assert_equal([:a], o.members)
68  end
69
70  def test_ref
71    klass = @Struct.new(:a)
72    o = klass.new(1)
73    assert_equal(1, o[:a])
74    assert_raise(NameError) { o[:b] }
75  end
76
77  def test_set
78    klass = @Struct.new(:a)
79    o = klass.new(1)
80    o[:a] = 2
81    assert_equal(2, o[:a])
82    assert_raise(NameError) { o[:b] = 3 }
83  end
84
85  def test_struct_new
86    assert_raise(NameError) { @Struct.new("foo") }
87    assert_nothing_raised { @Struct.new("Foo") }
88    @Struct.instance_eval { remove_const(:Foo) }
89    assert_nothing_raised { @Struct.new(:a) { } }
90    assert_raise(RuntimeError) { @Struct.new(:a) { raise } }
91
92    assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members)
93  end
94
95  def test_struct_new_with_empty_hash
96    assert_equal({:a=>1}, Struct.new(:a, {}).new({:a=>1}).a)
97  end
98
99  def test_struct_new_with_keyword_init
100    @Struct.new("KeywordInitTrue", :a, :b, keyword_init: true)
101    @Struct.new("KeywordInitFalse", :a, :b, keyword_init: false)
102
103    assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, 2) }
104    assert_nothing_raised { @Struct::KeywordInitFalse.new(1, 2) }
105    assert_nothing_raised { @Struct::KeywordInitTrue.new(a: 1, b: 2) }
106    assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, b: 2) }
107    assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(a: 1, b: 2, c: 3) }
108    assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values
109    assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect
110    assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect
111
112    @Struct.instance_eval do
113      remove_const(:KeywordInitTrue)
114      remove_const(:KeywordInitFalse)
115    end
116  end
117
118  def test_initialize
119    klass = @Struct.new(:a)
120    assert_raise(ArgumentError) { klass.new(1, 2) }
121    klass = @Struct.new(:total) do
122      def initialize(a, b)
123        super(a+b)
124      end
125    end
126    assert_equal 3, klass.new(1,2).total
127  end
128
129  def test_each
130    klass = @Struct.new(:a, :b)
131    o = klass.new(1, 2)
132    assert_equal([1, 2], o.each.to_a)
133  end
134
135  def test_each_pair
136    klass = @Struct.new(:a, :b)
137    o = klass.new(1, 2)
138    assert_equal([[:a, 1], [:b, 2]], o.each_pair.to_a)
139    bug7382 = '[ruby-dev:46533]'
140    a = []
141    o.each_pair {|x| a << x}
142    assert_equal([[:a, 1], [:b, 2]], a, bug7382)
143  end
144
145  def test_inspect
146    klass = @Struct.new(:a)
147    o = klass.new(1)
148    assert_equal("#<struct a=1>", o.inspect)
149    o.a = o
150    assert_match(/^#<struct a=#<struct #<.*?>:...>>$/, o.inspect)
151
152    @Struct.new("Foo", :a)
153    o = @Struct::Foo.new(1)
154    assert_equal("#<struct #@Struct::Foo a=1>", o.inspect)
155    @Struct.instance_eval { remove_const(:Foo) }
156
157    klass = @Struct.new(:a, :b)
158    o = klass.new(1, 2)
159    assert_equal("#<struct a=1, b=2>", o.inspect)
160
161    klass = @Struct.new(:@a)
162    o = klass.new(1)
163    assert_equal(1, o.__send__(:@a))
164    assert_equal("#<struct :@a=1>", o.inspect)
165    o.__send__(:"@a=", 2)
166    assert_equal(2, o.__send__(:@a))
167    assert_equal("#<struct :@a=2>", o.inspect)
168    o.__send__("@a=", 3)
169    assert_equal(3, o.__send__(:@a))
170    assert_equal("#<struct :@a=3>", o.inspect)
171
172    methods = klass.instance_methods(false)
173    assert_equal([:@a, :"@a="].sort.inspect, methods.sort.inspect, '[Bug #8756]')
174    assert_include(methods, :@a)
175    assert_include(methods, :"@a=")
176  end
177
178  def test_init_copy
179    klass = @Struct.new(:a)
180    o = klass.new(1)
181    assert_equal(o, o.dup)
182  end
183
184  def test_aref
185    klass = @Struct.new(:a)
186    o = klass.new(1)
187    assert_equal(1, o[0])
188    assert_raise_with_message(IndexError, /offset -2\b/) {o[-2]}
189    assert_raise_with_message(IndexError, /offset 1\b/) {o[1]}
190    assert_raise_with_message(NameError, /foo/) {o["foo"]}
191    assert_raise_with_message(NameError, /foo/) {o[:foo]}
192  end
193
194  def test_aset
195    klass = @Struct.new(:a)
196    o = klass.new(1)
197    o[0] = 2
198    assert_equal(2, o[:a])
199    assert_raise_with_message(IndexError, /offset -2\b/) {o[-2] = 3}
200    assert_raise_with_message(IndexError, /offset 1\b/) {o[1] = 3}
201    assert_raise_with_message(NameError, /foo/) {o["foo"] = 3}
202    assert_raise_with_message(NameError, /foo/) {o[:foo] = 3}
203  end
204
205  def test_values_at
206    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
207    o = klass.new(1, 2, 3, 4, 5, 6)
208    assert_equal([2, 4, 6], o.values_at(1, 3, 5))
209    assert_equal([2, 3, 4, 3, 4, 5], o.values_at(1..3, 2...5))
210  end
211
212  def test_select
213    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
214    o = klass.new(1, 2, 3, 4, 5, 6)
215    assert_equal([1, 3, 5], o.select {|v| v % 2 != 0 })
216    assert_raise(ArgumentError) { o.select(1) }
217  end
218
219  def test_filter
220    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
221    o = klass.new(1, 2, 3, 4, 5, 6)
222    assert_equal([1, 3, 5], o.filter {|v| v % 2 != 0 })
223    assert_raise(ArgumentError) { o.filter(1) }
224  end
225
226  def test_big_struct
227    klass1 = @Struct.new(*('a'..'z').map(&:to_sym))
228    o = klass1.new
229    assert_nil o.z
230    assert_equal(:foo, o.z = :foo)
231    assert_equal(:foo, o.z)
232    assert_equal(:foo, o[25])
233  end
234
235  def test_overridden_aset
236    bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by []= method'
237
238    struct = Class.new(Struct.new(*(:a..:z), :result)) do
239      def []=(*args)
240        raise args.inspect
241      end
242    end
243
244    obj = struct.new
245    assert_nothing_raised(RuntimeError, bug10601) do
246      obj.result = 42
247    end
248    assert_equal(42, obj.result, bug10601)
249  end
250
251  def test_overridden_aref
252    bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by [] method'
253
254    struct = Class.new(Struct.new(*(:a..:z), :result)) do
255      def [](*args)
256        raise args.inspect
257      end
258    end
259
260    obj = struct.new
261    obj.result = 42
262    result = assert_nothing_raised(RuntimeError, bug10601) do
263      break obj.result
264    end
265    assert_equal(42, result, bug10601)
266  end
267
268  def test_equal
269    klass1 = @Struct.new(:a)
270    klass2 = @Struct.new(:a, :b)
271    o1 = klass1.new(1)
272    o2 = klass1.new(1)
273    o3 = klass2.new(1)
274    assert_equal(o1, o2)
275    assert_not_equal(o1, o3)
276  end
277
278  def test_hash
279    klass = @Struct.new(:a)
280    o = klass.new(1)
281    assert_kind_of(Integer, o.hash)
282  end
283
284  def test_eql
285    klass1 = @Struct.new(:a)
286    klass2 = @Struct.new(:a, :b)
287    o1 = klass1.new(1)
288    o2 = klass1.new(1)
289    o3 = klass2.new(1)
290    assert_operator(o1, :eql?, o2)
291    assert_not_operator(o1, :eql?, o3)
292  end
293
294  def test_size
295    klass = @Struct.new(:a)
296    o = klass.new(1)
297    assert_equal(1, o.size)
298  end
299
300  def test_error
301    assert_raise(TypeError){
302      @Struct.new(0)
303    }
304  end
305
306  def test_redefinition_warning
307    @Struct.new("RedefinitionWarning")
308    e = EnvUtil.verbose_warning do
309      @Struct.new("RedefinitionWarning")
310    end
311    assert_match(/redefining constant #@Struct::RedefinitionWarning/, e)
312  end
313
314  def test_nonascii
315    struct_test = @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
316    assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]')
317    a = struct_test.new(42)
318    assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]')
319    e = EnvUtil.verbose_warning do
320      @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
321    end
322    assert_nothing_raised(Encoding::CompatibilityError) do
323      assert_match(/redefining constant #@Struct::R\u{e9}sum\u{e9}/, e)
324    end
325  end
326
327  def test_junk
328    struct_test = @Struct.new("Foo", "a\000")
329    o = struct_test.new(1)
330    assert_equal(1, o.send("a\000"))
331    @Struct.instance_eval { remove_const(:Foo) }
332  end
333
334  def test_comparison_when_recursive
335    klass1 = @Struct.new(:a, :b, :c)
336
337    x = klass1.new(1, 2, nil); x.c = x
338    y = klass1.new(1, 2, nil); y.c = y
339    Timeout.timeout(1) {
340      assert_equal x, y
341      assert_operator x, :eql?, y
342    }
343
344    z = klass1.new(:something, :other, nil); z.c = z
345    Timeout.timeout(1) {
346      assert_not_equal x, z
347      assert_not_operator x, :eql?, z
348    }
349
350    x.c = y; y.c = x
351    Timeout.timeout(1) {
352      assert_equal x, y
353      assert_operator x, :eql?, y
354    }
355
356    x.c = z; z.c = x
357    Timeout.timeout(1) {
358      assert_not_equal x, z
359      assert_not_operator x, :eql?, z
360    }
361  end
362
363  def test_to_h
364    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
365    o = klass.new(1, 2, 3, 4, 5, 6)
366    assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h)
367  end
368
369  def test_to_h_block
370    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
371    o = klass.new(1, 2, 3, 4, 5, 6)
372    assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36},
373                 o.to_h {|k, v| [k.to_s, v*v]})
374  end
375
376  def test_question_mark_in_member
377    klass = @Struct.new(:a, :b?)
378    x = Object.new
379    o = klass.new("test", x)
380    assert_same(x, o.b?)
381    o.send("b?=", 42)
382    assert_equal(42, o.b?)
383  end
384
385  def test_bang_mark_in_member
386    klass = @Struct.new(:a, :b!)
387    x = Object.new
388    o = klass.new("test", x)
389    assert_same(x, o.b!)
390    o.send("b!=", 42)
391    assert_equal(42, o.b!)
392  end
393
394  def test_setter_method_returns_value
395    klass = @Struct.new(:a)
396    x = klass.new
397    assert_equal "[Bug #9353]", x.send(:a=, "[Bug #9353]")
398  end
399
400  def test_dig
401    klass = @Struct.new(:a)
402    o = klass.new(klass.new({b: [1, 2, 3]}))
403    assert_equal(1, o.dig(:a, :a, :b, 0))
404    assert_nil(o.dig(:b, 0))
405  end
406
407  def test_new_dupilicate
408    bug12291 = '[ruby-core:74971] [Bug #12291]'
409    assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
410      @Struct.new(:a, :a)
411    }
412  end
413
414  class TopStruct < Test::Unit::TestCase
415    include TestStruct
416
417    def initialize(*)
418      super
419      @Struct = Struct
420    end
421  end
422
423  class SubStruct < Test::Unit::TestCase
424    include TestStruct
425    SubStruct = Class.new(Struct)
426
427    def initialize(*)
428      super
429      @Struct = SubStruct
430    end
431  end
432end
433