1require 'test/unit' 2 3require 'tempfile' 4require_relative 'envutil' 5require 'tmpdir' 6 7class TestRequire < Test::Unit::TestCase 8 def test_load_error_path 9 filename = "should_not_exist" 10 error = assert_raise(LoadError) do 11 require filename 12 end 13 assert_equal filename, error.path 14 end 15 16 def test_require_invalid_shared_object 17 t = Tempfile.new(["test_ruby_test_require", ".so"]) 18 t.puts "dummy" 19 t.close 20 21 assert_in_out_err([], <<-INPUT, %w(:ok), []) 22 $:.replace([IO::NULL]) 23 begin 24 require \"#{ t.path }\" 25 rescue LoadError 26 p :ok 27 end 28 INPUT 29 t.close(true) 30 end 31 32 def test_require_too_long_filename 33 assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), []) 34 $:.replace([IO::NULL]) 35 begin 36 require '#{ "foo/" * 10000 }foo' 37 rescue LoadError 38 p :ok 39 end 40 INPUT 41 42 begin 43 assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e| 44 assert_equal([], r) 45 assert_operator(2, :<=, e.size) 46 assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first) 47 assert_match(/\(LoadError\)/, e.last) 48 end 49 rescue Errno::EINVAL 50 # too long commandline may be blocked by OS. 51 end 52 end 53 54 def test_require_nonascii 55 bug3758 = '[ruby-core:31915]' 56 ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path| 57 e = assert_raise(LoadError, bug3758) {require path} 58 assert_match(/#{path}\z/, e.message, bug3758) 59 end 60 end 61 62 def test_require_nonascii_path 63 bug8165 = '[ruby-core:53733] [Bug #8165]' 64 Dir.mktmpdir {|tmp| 65 encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? 'filesystem' : 'UTF-8' 66 dir = "\u3042" * 5 67 begin 68 require_path = File.join(tmp, dir, 'foo.rb').encode(encoding) 69 rescue 70 skip "cannot convert path encoding to #{encoding}" 71 end 72 Dir.mkdir(File.dirname(require_path)) 73 open(require_path, "wb") {} 74 assert_separately(%w[--disable=gems], <<-INPUT) 75 # leave paths for require encoding objects 76 bug = "#{bug8165} require #{encoding} path" 77 require_path = "#{require_path}" 78 enc_path = Regexp.new(Regexp.escape(RUBY_PLATFORM)) 79 $:.replace([IO::NULL] + $:.reject {|path| enc_path !~ path}) 80 assert_nothing_raised(LoadError, bug) { 81 assert(require(require_path), bug) 82 assert(!require(require_path), bug) 83 } 84 INPUT 85 } 86 end 87 88 def test_require_path_home_1 89 env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] 90 pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m 91 92 ENV["RUBYPATH"] = "~" 93 ENV["HOME"] = "/foo" * 1024 94 assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long) 95 96 ensure 97 env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") 98 env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") 99 end 100 101 def test_require_path_home_2 102 env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] 103 pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m 104 105 ENV["RUBYPATH"] = "~" + "/foo" * 1024 106 ENV["HOME"] = "/foo" 107 assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long) 108 109 ensure 110 env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") 111 env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") 112 end 113 114 def test_require_path_home_3 115 env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] 116 117 t = Tempfile.new(["test_ruby_test_require", ".rb"]) 118 t.puts "p :ok" 119 t.close 120 121 ENV["RUBYPATH"] = "~" 122 ENV["HOME"] = t.path 123 assert_in_out_err(%w(-S test_ruby_test_require), "", [], /\(LoadError\)/) 124 125 ENV["HOME"], name = File.split(t.path) 126 assert_in_out_err(["-S", name], "", %w(:ok), []) 127 128 ensure 129 env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") 130 env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") 131 t.close(true) 132 end 133 134 def test_require_with_unc 135 ruby = File.expand_path(EnvUtil.rubybin).sub(/\A(\w):/, '//127.0.0.1/\1$/') 136 skip "local drive #$1: is not shared" unless File.exist?(ruby) 137 pid = nil 138 assert_nothing_raised {pid = spawn(ruby, "-rabbrev", "-e0")} 139 ret, status = Process.wait2(pid) 140 assert_equal(pid, ret) 141 assert_predicate(status, :success?) 142 end if /mswin|mingw/ =~ RUBY_PLATFORM 143 144 def test_require_twice 145 Dir.mktmpdir do |tmp| 146 req = File.join(tmp, "very_long_file_name.rb") 147 File.write(req, "p :ok\n") 148 assert_file.exist?(req) 149 req[/.rb$/i] = "" 150 assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), []) 151 require "#{req}" 152 require "#{req}" 153 INPUT 154 end 155 end 156 157 def test_define_class 158 begin 159 require "socket" 160 rescue LoadError 161 return 162 end 163 164 assert_in_out_err([], <<-INPUT, %w(:ok), []) 165 BasicSocket = 1 166 begin 167 require 'socket' 168 p :ng 169 rescue TypeError 170 p :ok 171 end 172 INPUT 173 174 assert_in_out_err([], <<-INPUT, %w(:ok), []) 175 class BasicSocket; end 176 begin 177 require 'socket' 178 p :ng 179 rescue TypeError 180 p :ok 181 end 182 INPUT 183 184 assert_in_out_err([], <<-INPUT, %w(:ok), []) 185 class BasicSocket < IO; end 186 begin 187 require 'socket' 188 p :ok 189 rescue Exception 190 p :ng 191 end 192 INPUT 193 end 194 195 def test_define_class_under 196 begin 197 require "zlib" 198 rescue LoadError 199 return 200 end 201 202 assert_in_out_err([], <<-INPUT, %w(:ok), []) 203 module Zlib; end 204 Zlib::Error = 1 205 begin 206 require 'zlib' 207 p :ng 208 rescue TypeError 209 p :ok 210 end 211 INPUT 212 213 assert_in_out_err([], <<-INPUT, %w(:ok), []) 214 module Zlib; end 215 class Zlib::Error; end 216 begin 217 require 'zlib' 218 p :ng 219 rescue NameError 220 p :ok 221 end 222 INPUT 223 224 assert_in_out_err([], <<-INPUT, %w(:ok), []) 225 module Zlib; end 226 class Zlib::Error < StandardError; end 227 begin 228 require 'zlib' 229 p :ok 230 rescue Exception 231 p :ng 232 end 233 INPUT 234 end 235 236 def test_define_module 237 begin 238 require "zlib" 239 rescue LoadError 240 return 241 end 242 243 assert_in_out_err([], <<-INPUT, %w(:ok), []) 244 Zlib = 1 245 begin 246 require 'zlib' 247 p :ng 248 rescue TypeError 249 p :ok 250 end 251 INPUT 252 end 253 254 def test_define_module_under 255 begin 256 require "socket" 257 rescue LoadError 258 return 259 end 260 261 assert_in_out_err([], <<-INPUT, %w(:ok), []) 262 class BasicSocket < IO; end 263 class Socket < BasicSocket; end 264 Socket::Constants = 1 265 begin 266 require 'socket' 267 p :ng 268 rescue TypeError 269 p :ok 270 end 271 INPUT 272 end 273 274 def test_load 275 t = Tempfile.new(["test_ruby_test_require", ".rb"]) 276 t.puts "module Foo; end" 277 t.puts "at_exit { p :wrap_end }" 278 t.puts "at_exit { raise 'error in at_exit test' }" 279 t.puts "p :ok" 280 t.close 281 282 assert_in_out_err([], <<-INPUT, %w(:ok :end :wrap_end), /error in at_exit test/) 283 load(#{ t.path.dump }, true) 284 GC.start 285 p :end 286 INPUT 287 288 assert_raise(ArgumentError) { at_exit } 289 t.close(true) 290 end 291 292 def test_load2 # [ruby-core:25039] 293 t = Tempfile.new(["test_ruby_test_require", ".rb"]) 294 t.puts "Hello = 'hello'" 295 t.puts "class Foo" 296 t.puts " p Hello" 297 t.puts "end" 298 t.close 299 300 assert_in_out_err([], <<-INPUT, %w("hello"), []) 301 load(#{ t.path.dump }, true) 302 INPUT 303 t.close(true) 304 end 305 306 def test_tainted_loadpath 307 t = Tempfile.new(["test_ruby_test_require", ".rb"]) 308 abs_dir, file = File.split(t.path) 309 abs_dir = File.expand_path(abs_dir).untaint 310 311 assert_in_out_err([], <<-INPUT, %w(:ok), []) 312 abs_dir = "#{ abs_dir }" 313 $: << abs_dir 314 require "#{ file }" 315 p :ok 316 INPUT 317 318 assert_in_out_err([], <<-INPUT, %w(:ok), []) 319 abs_dir = "#{ abs_dir }" 320 $: << abs_dir.taint 321 require "#{ file }" 322 p :ok 323 INPUT 324 325 assert_in_out_err([], <<-INPUT, %w(:ok), []) 326 abs_dir = "#{ abs_dir }" 327 $: << abs_dir.taint 328 $SAFE = 1 329 begin 330 require "#{ file }" 331 rescue SecurityError 332 p :ok 333 end 334 INPUT 335 336 assert_in_out_err([], <<-INPUT, %w(:ok), []) 337 abs_dir = "#{ abs_dir }" 338 $: << abs_dir.taint 339 $SAFE = 1 340 begin 341 require "#{ file }" 342 rescue SecurityError 343 p :ok 344 end 345 INPUT 346 347 assert_in_out_err([], <<-INPUT, %w(:ok), []) 348 abs_dir = "#{ abs_dir }" 349 $: << abs_dir << 'elsewhere'.taint 350 require "#{ file }" 351 p :ok 352 INPUT 353 354 t.close(true) 355 end 356 357 def test_relative 358 load_path = $:.dup 359 $:.delete(".") 360 Dir.mktmpdir do |tmp| 361 Dir.chdir(tmp) do 362 Dir.mkdir('x') 363 File.open('x/t.rb', 'wb') {} 364 File.open('x/a.rb', 'wb') {|f| f.puts("require_relative('t.rb')")} 365 assert require('./x/t.rb') 366 assert !require(File.expand_path('x/t.rb')) 367 assert_nothing_raised(LoadError) {require('./x/a.rb')} 368 assert_raise(LoadError) {require('x/t.rb')} 369 File.unlink(*Dir.glob('x/*')) 370 Dir.rmdir("#{tmp}/x") 371 $:.replace(load_path) 372 load_path = nil 373 assert(!require('tmpdir')) 374 end 375 end 376 ensure 377 $:.replace(load_path) if load_path 378 end 379 380 def test_relative_symlink 381 Dir.mktmpdir {|tmp| 382 Dir.chdir(tmp) { 383 Dir.mkdir "a" 384 Dir.mkdir "b" 385 File.open("a/lib.rb", "w") {|f| f.puts 'puts "a/lib.rb"' } 386 File.open("b/lib.rb", "w") {|f| f.puts 'puts "b/lib.rb"' } 387 File.open("a/tst.rb", "w") {|f| f.puts 'require_relative "lib"' } 388 begin 389 File.symlink("../a/tst.rb", "b/tst.rb") 390 result = IO.popen([EnvUtil.rubybin, "b/tst.rb"]).read 391 assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]") 392 rescue NotImplementedError 393 skip "File.symlink is not implemented" 394 end 395 } 396 } 397 end 398 399 def test_frozen_loaded_features 400 bug3756 = '[ruby-core:31913]' 401 assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "", 402 [], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/, 403 bug3756) 404 end 405 406 def test_race_exception 407 bug5754 = '[ruby-core:41618]' 408 tmp = Tempfile.new(%w"bug5754 .rb") 409 path = tmp.path 410 tmp.print %{\ 411 th = Thread.current 412 t = th[:t] 413 scratch = th[:scratch] 414 415 if scratch.empty? 416 scratch << :pre 417 Thread.pass until t.stop? 418 raise RuntimeError 419 else 420 scratch << :post 421 end 422 } 423 tmp.close 424 425 # "circular require" warnings to $stderr, but backtraces to stderr 426 # in C-level. And redirecting stderr to a pipe seems to change 427 # some blocking timings and causes a deadlock, so run in a 428 # separated process for the time being. 429 assert_separately(["-w", "-", path, bug5754], <<-'end;', ignore_stderr: true) 430 path, bug5754 = *ARGV 431 start = false 432 433 scratch = [] 434 t1_res = nil 435 t2_res = nil 436 437 t1 = Thread.new do 438 Thread.pass until start 439 begin 440 require(path) 441 rescue RuntimeError 442 end 443 444 t1_res = require(path) 445 end 446 447 t2 = Thread.new do 448 Thread.pass until scratch[0] 449 t2_res = require(path) 450 end 451 452 t1[:scratch] = t2[:scratch] = scratch 453 t1[:t] = t2 454 t2[:t] = t1 455 456 start = true 457 458 assert_nothing_raised(ThreadError, bug5754) {t1.join} 459 assert_nothing_raised(ThreadError, bug5754) {t2.join} 460 461 assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}") 462 assert_equal([:pre, :post], scratch, bug5754) 463 end; 464 ensure 465 $".delete(path) 466 tmp.close(true) if tmp 467 end 468 469 def test_loaded_features_encoding 470 bug6377 = '[ruby-core:44750]' 471 loadpath = $:.dup 472 features = $".dup 473 $".clear 474 $:.clear 475 Dir.mktmpdir {|tmp| 476 $: << tmp 477 open(File.join(tmp, "foo.rb"), "w") {} 478 require "foo" 479 assert(Encoding.compatible?(tmp, $"[0]), bug6377) 480 } 481 ensure 482 $:.replace(loadpath) 483 $".replace(features) 484 end 485 486 def test_require_changed_current_dir 487 bug7158 = '[ruby-core:47970]' 488 Dir.mktmpdir {|tmp| 489 Dir.chdir(tmp) { 490 Dir.mkdir("a") 491 Dir.mkdir("b") 492 open(File.join("a", "foo.rb"), "w") {} 493 open(File.join("b", "bar.rb"), "w") {|f| 494 f.puts "p :ok" 495 } 496 assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) 497 $:.replace([IO::NULL]) 498 $: << "." 499 Dir.chdir("a") 500 require "foo" 501 Dir.chdir("../b") 502 p :ng unless require "bar" 503 Dir.chdir("..") 504 p :ng if require "b/bar" 505 INPUT 506 } 507 } 508 end 509 510 def test_require_not_modified_load_path 511 bug7158 = '[ruby-core:47970]' 512 Dir.mktmpdir {|tmp| 513 Dir.chdir(tmp) { 514 open("foo.rb", "w") {} 515 assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) 516 $:.replace([IO::NULL]) 517 a = Object.new 518 def a.to_str 519 "#{tmp}" 520 end 521 $: << a 522 require "foo" 523 last_path = $:.pop 524 p :ok if last_path == a && last_path.class == Object 525 INPUT 526 } 527 } 528 end 529 530 def test_require_changed_home 531 bug7158 = '[ruby-core:47970]' 532 Dir.mktmpdir {|tmp| 533 Dir.chdir(tmp) { 534 open("foo.rb", "w") {} 535 Dir.mkdir("a") 536 open(File.join("a", "bar.rb"), "w") {} 537 assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) 538 $:.replace([IO::NULL]) 539 $: << '~' 540 ENV['HOME'] = "#{tmp}" 541 require "foo" 542 ENV['HOME'] = "#{tmp}/a" 543 p :ok if require "bar" 544 INPUT 545 } 546 } 547 end 548 549 def test_require_to_path_redefined_in_load_path 550 bug7158 = '[ruby-core:47970]' 551 Dir.mktmpdir {|tmp| 552 Dir.chdir(tmp) { 553 open("foo.rb", "w") {} 554 assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) 555 $:.replace([IO::NULL]) 556 a = Object.new 557 def a.to_path 558 "bar" 559 end 560 $: << a 561 begin 562 require "foo" 563 p [:ng, $LOAD_PATH, ENV['RUBYLIB']] 564 rescue LoadError 565 end 566 def a.to_path 567 "#{tmp}" 568 end 569 p :ok if require "foo" 570 INPUT 571 } 572 } 573 end 574 575 def test_require_to_str_redefined_in_load_path 576 bug7158 = '[ruby-core:47970]' 577 Dir.mktmpdir {|tmp| 578 Dir.chdir(tmp) { 579 open("foo.rb", "w") {} 580 assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) 581 $:.replace([IO::NULL]) 582 a = Object.new 583 def a.to_str 584 "foo" 585 end 586 $: << a 587 begin 588 require "foo" 589 p [:ng, $LOAD_PATH, ENV['RUBYLIB']] 590 rescue LoadError 591 end 592 def a.to_str 593 "#{tmp}" 594 end 595 p :ok if require "foo" 596 INPUT 597 } 598 } 599 end 600 601 def assert_require_with_shared_array_modified(add, del) 602 bug7383 = '[ruby-core:49518]' 603 Dir.mktmpdir {|tmp| 604 Dir.chdir(tmp) { 605 open("foo.rb", "w") {} 606 Dir.mkdir("a") 607 open(File.join("a", "bar.rb"), "w") {} 608 assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7383) 609 $:.replace([IO::NULL]) 610 $:.#{add} "#{tmp}" 611 $:.#{add} "#{tmp}/a" 612 require "foo" 613 $:.#{del} 614 # Expanded load path cache should be rebuilt. 615 begin 616 require "bar" 617 rescue LoadError 618 p :ok 619 end 620 INPUT 621 } 622 } 623 end 624 625 def test_require_with_array_pop 626 assert_require_with_shared_array_modified("push", "pop") 627 end 628 629 def test_require_with_array_shift 630 assert_require_with_shared_array_modified("unshift", "shift") 631 end 632 633 def test_require_local_var_on_toplevel 634 bug7536 = '[ruby-core:50701]' 635 Dir.mktmpdir {|tmp| 636 Dir.chdir(tmp) { 637 open("bar.rb", "w") {|f| f.puts 'TOPLEVEL_BINDING.eval("lib = 2")' } 638 assert_in_out_err(%w[-r./bar.rb], <<-INPUT, %w([:lib] 2), [], bug7536) 639 puts TOPLEVEL_BINDING.eval("local_variables").inspect 640 puts TOPLEVEL_BINDING.eval("lib").inspect 641 INPUT 642 } 643 } 644 end 645 646 def test_require_with_loaded_features_pop 647 bug7530 = '[ruby-core:50645]' 648 script = Tempfile.new(%w'bug-7530- .rb') 649 script.close 650 assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], <<-INPUT, %w(:ok), [], bug7530) 651 PATH = ARGV.shift 652 THREADS = 2 653 ITERATIONS_PER_THREAD = 1000 654 655 THREADS.times.map { 656 Thread.new do 657 ITERATIONS_PER_THREAD.times do 658 require PATH 659 $".pop 660 end 661 end 662 }.each(&:join) 663 p :ok 664 INPUT 665 ensure 666 script.close(true) if script 667 end 668end 669