summaryrefslogtreecommitdiff
path: root/test/ruby/test_autoload.rb
blob: a672e0be5a294037953b8c91e227249366e500bc (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
require 'test/unit'
require 'tempfile'
require 'thread'

class TestAutoload < Test::Unit::TestCase
  def test_autoload_so
    # Date is always available, unless excluded intentionally.
    assert_in_out_err([], <<-INPUT, [], [])
    autoload :Date, "date"
    begin Date; rescue LoadError; end
    INPUT
  end

  def test_non_realpath_in_loadpath
    require 'tmpdir'
    tmpdir = Dir.mktmpdir('autoload')
    tmpdirs = [tmpdir]
    tmpdirs.unshift(tmpdir + '/foo')
    Dir.mkdir(tmpdirs[0])
    tmpfiles = [tmpdir + '/foo.rb', tmpdir + '/foo/bar.rb']
    open(tmpfiles[0] , 'w') do |f|
      f.puts <<-INPUT
$:.unshift(File.expand_path('..', __FILE__)+'/./foo')
module Foo
  autoload :Bar, 'bar'
end
p Foo::Bar
      INPUT
    end
    open(tmpfiles[1], 'w') do |f|
      f.puts 'class Foo::Bar; end'
    end
    assert_in_out_err([tmpfiles[0]], "", ["Foo::Bar"], [])
  ensure
    File.unlink(*tmpfiles) rescue nil if tmpfiles
    tmpdirs.each {|dir| Dir.rmdir(dir)}
  end

  def test_autoload_p
    bug4565 = '[ruby-core:35679]'

    require 'tmpdir'
    Dir.mktmpdir('autoload') {|tmpdir|
      tmpfile = tmpdir + '/foo.rb'
      a = Module.new do
        autoload :X, tmpfile
      end
      b = Module.new do
        include a
      end
      assert_equal(true, a.const_defined?(:X))
      assert_equal(true, b.const_defined?(:X))
      assert_equal(tmpfile, a.autoload?(:X), bug4565)
      assert_equal(tmpfile, b.autoload?(:X), bug4565)
    }
  end

  def test_autoload_with_unqualified_file_name # [ruby-core:69206]
    lp = $LOAD_PATH.dup
    lf = $LOADED_FEATURES.dup

    Dir.mktmpdir('autoload') { |tmpdir|
      $LOAD_PATH << tmpdir

      Dir.chdir(tmpdir) do
        eval <<-END
          class ::Object
            module A
              autoload :C, 'b'
            end
          end
        END

        File.open('b.rb', 'w') {|file| file.puts 'module A; class C; end; end'}
        assert_kind_of Class, ::A::C
      end
    }
  ensure
    $LOAD_PATH.replace lp
    $LOADED_FEATURES.replace lf
    Object.send(:remove_const, :A) if Object.const_defined?(:A)
  end

  def test_require_explicit
    Tempfile.create(['autoload', '.rb']) {|file|
      file.puts 'class Object; AutoloadTest = 1; end'
      file.close
      add_autoload(file.path)
      begin
        assert_nothing_raised do
          assert(require file.path)
          assert_equal(1, ::AutoloadTest)
        end
      ensure
        remove_autoload_constant
      end
    }
  end

  def test_threaded_accessing_constant
    # Suppress "warning: loading in progress, circular require considered harmful"
    EnvUtil.default_warning {
      Tempfile.create(['autoload', '.rb']) {|file|
        file.puts 'sleep 0.5; class AutoloadTest; X = 1; end'
        file.close
        add_autoload(file.path)
        begin
          assert_nothing_raised do
            t1 = Thread.new { ::AutoloadTest::X }
            t2 = Thread.new { ::AutoloadTest::X }
            [t1, t2].each(&:join)
          end
        ensure
          remove_autoload_constant
        end
      }
    }
  end

  def test_threaded_accessing_inner_constant
    # Suppress "warning: loading in progress, circular require considered harmful"
    EnvUtil.default_warning {
      Tempfile.create(['autoload', '.rb']) {|file|
        file.puts 'class AutoloadTest; sleep 0.5; X = 1; end'
        file.close
        add_autoload(file.path)
        begin
          assert_nothing_raised do
            t1 = Thread.new { ::AutoloadTest::X }
            t2 = Thread.new { ::AutoloadTest::X }
            [t1, t2].each(&:join)
          end
        ensure
          remove_autoload_constant
        end
      }
    }
  end

  def test_nameerror_when_autoload_did_not_define_the_constant
    Tempfile.create(['autoload', '.rb']) {|file|
      file.puts ''
      file.close
      add_autoload(file.path)
      begin
        assert_raise(NameError) do
          AutoloadTest
        end
      ensure
        remove_autoload_constant
      end
    }
  end

  def test_override_autoload
    Tempfile.create(['autoload', '.rb']) {|file|
      file.puts ''
      file.close
      add_autoload(file.path)
      begin
        eval %q(class AutoloadTest; end)
        assert_equal(Class, AutoloadTest.class)
      ensure
        remove_autoload_constant
      end
    }
  end

  def test_override_while_autoloading
    Tempfile.create(['autoload', '.rb']) {|file|
      file.puts 'class AutoloadTest; sleep 0.5; end'
      file.close
      add_autoload(file.path)
      begin
        # while autoloading...
        t = Thread.new { AutoloadTest }
        sleep 0.1
        # override it
        EnvUtil.suppress_warning {
          eval %q(AutoloadTest = 1)
        }
        t.join
        assert_equal(1, AutoloadTest)
      ensure
        remove_autoload_constant
      end
    }
  end

  def ruby_impl_require
    Kernel.module_eval do; alias :old_require :require; end
    called_with = []
    Kernel.send :define_method, :require do |path|
      called_with << path
      old_require path
    end
    yield called_with
  ensure
    Kernel.module_eval do; alias :require :old_require; undef :old_require; end
  end

  def test_require_implemented_in_ruby_is_called
    ruby_impl_require do |called_with|
      Tempfile.create(['autoload', '.rb']) {|file|
        file.puts 'class AutoloadTest; end'
        file.close
        add_autoload(file.path)
        begin
          assert(Object::AutoloadTest)
        ensure
          remove_autoload_constant
        end
        assert_equal [file.path], called_with
      }
    end
  end

  def test_autoload_while_autoloading
    ruby_impl_require do |called_with|
      Tempfile.create(%w(a .rb)) do |a|
        Tempfile.create(%w(b .rb)) do |b|
          a.puts "require '#{b.path}'; class AutoloadTest; end"
          b.puts "class AutoloadTest; module B; end; end"
          [a, b].each(&:flush)
          add_autoload(a.path)
          begin
            assert(Object::AutoloadTest)
          ensure
            remove_autoload_constant
          end
          assert_equal [a.path, b.path], called_with
        end
      end
    end
  end

  def add_autoload(path)
    (@autoload_paths ||= []) << path
    ::Object.class_eval {autoload(:AutoloadTest, path)}
  end

  def remove_autoload_constant
    $".replace($" - @autoload_paths)
    ::Object.class_eval {remove_const(:AutoloadTest)}
  end
end