diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ffe2272 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +script: + - "ruby run_test.rb all test" diff --git a/README.md b/README.md index f08f5da..1368d47 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ mruby-require ============= +[![Build Status](https://travis-ci.org/iij/mruby-require.svg?branch=master)](https://travis-ci.org/iij/mruby-require) "mruby-require" is a mrbgem, provides [require](http://docs.ruby-lang.org/ja/2.0.0/class/Kernel.html#M_REQUIRE) and @@ -30,14 +31,6 @@ end ``` -### To build: - -Depend mrbgems: - - * [iij/mruby-io](https://github.com/iij/mruby-io) - * [iij/mruby-dir](https://github.com/iij/mruby-dir) - * [iij/mruby-tempfile](https://github.com/iij/mruby-tempfile) - ### To run the tests: ruby run_test.rb diff --git a/mrbgem.rake b/mrbgem.rake index ccee3c3..5f64350 100644 --- a/mrbgem.rake +++ b/mrbgem.rake @@ -2,10 +2,13 @@ MRuby::Gem::Specification.new('mruby-require') do |spec| spec.license = 'MIT' spec.authors = 'Internet Initiative Japan Inc.' - ['mruby-io', 'mruby-dir', 'mruby-tempfile'].each do |v| - add_dependency v - end + spec.add_dependency 'mruby-array-ext' + spec.add_dependency 'mruby-eval' + spec.add_dependency 'mruby-io' + + spec.add_test_dependency 'mruby-dir' + spec.add_test_dependency 'mruby-tempfile' + spec.add_test_dependency 'mruby-time' spec.cc.include_paths << "#{build.root}/src" end - diff --git a/mrblib/require.rb b/mrblib/require.rb index 3cf6255..1757a0a 100644 --- a/mrblib/require.rb +++ b/mrblib/require.rb @@ -1,26 +1,29 @@ class LoadError < ScriptError; end +$__mruby_require_toplevel_self__ = self + module Kernel def load(path) - raise NotImplementedError.new "'require' method depends on File" unless Object.const_defined?(:File) - raise TypeError unless path.class == String + raise TypeError unless path.class == String if File.exist?(path) && File.extname(path) == ".mrb" _load_mrb_file path elsif File.exist?(path) - _load_rb_str File.open(path).read.to_s, path + # _load_rb_str File.open(path).read.to_s, path + $__mruby_require_toplevel_self__.eval(File.open(path).read.to_s, nil, path) else - raise LoadError.new "File not found -- #{path}" + raise LoadError, "File not found -- #{path}" end + + true end def require(path) - raise NotImplementedError.new "'require' method depends on File" unless Object.const_defined?(:File) - raise TypeError unless path.class == String + raise TypeError unless path.class == String # require method can load .rb, .mrb or without-ext filename only. unless ["", ".rb", ".mrb"].include? File.extname(path) - raise LoadError.new "cannot load such file -- #{path}" + raise LoadError, "cannot load such file -- #{path}" end filenames = [] @@ -35,32 +38,26 @@ def require(path) filename = nil if ['/', '.'].include? path[0] path0 = filenames.find do |fname| - File.file?(fname) && File.exist?(fname) + ::File.file?(fname) end else dir = ($LOAD_PATH || []).find do |dir0| filename = filenames.find do |fname| - path0 = File.join dir0, fname - File.file?(path0) && File.exist?(path0) + path0 = ::File.join dir0, fname + ::File.file?(path0) end end path0 = dir && filename ? File.join(dir, filename) : nil end - if path0 && File.exist?(path0) && File.file?(path0) - __require__ path0 - else - raise LoadError.new "cannot load such file -- #{path}" + unless path0 && File.file?(path0) + raise LoadError, "cannot load such file -- #{path}" end - end - def __require__(realpath) - raise LoadError.new "File not found -- #{realpath}" unless File.exist? realpath - $" ||= [] - $__mruby_loading_files__ ||= [] + realpath = File.realpath(path0) # already required - return false if ($" + $__mruby_loading_files__).include?(realpath) + return false if ($" + $__mruby_loading_files__).include?(realpath) $__mruby_loading_files__ << realpath load realpath @@ -74,11 +71,9 @@ def __require__(realpath) $LOAD_PATH ||= [] $LOAD_PATH << '.' - if Object.const_defined?(:ENV) $LOAD_PATH.unshift(*ENV['MRBLIB'].split(':')) unless ENV['MRBLIB'].nil? end - $LOAD_PATH.uniq! $" ||= [] diff --git a/run_test.rb b/run_test.rb index d1e6fec..a3aa0b1 100644 --- a/run_test.rb +++ b/run_test.rb @@ -20,7 +20,11 @@ toolchain :gcc conf.gembox 'default' - conf.gem :git => 'https://github.com/iij/mruby-io.git' + enable_debug + enable_test + + conf.gem :core => 'mruby-eval' + conf.gem :core => 'mruby-io' conf.gem :git => 'https://github.com/iij/mruby-dir.git' conf.gem :git => 'https://github.com/iij/mruby-tempfile.git' diff --git a/src/require.c b/src/require.c index 5d90c22..2914e1c 100644 --- a/src/require.c +++ b/src/require.c @@ -1,4 +1,6 @@ +#if !(defined(_WIN32) || defined(_WIN64)) #include +#endif #include #include #include @@ -17,11 +19,41 @@ #define E_LOAD_ERROR (mrb_class_get(mrb, "LoadError")) +/* We can't use MRUBY_RELEASE_NO to determine if byte code implementation is old */ +#ifdef MKOP_A +#define USE_MRUBY_OLD_BYTE_CODE +#endif + #if MRUBY_RELEASE_NO < 10000 mrb_value mrb_yield_internal(mrb_state *mrb, mrb_value b, int argc, mrb_value *argv, mrb_value self, struct RClass *c); #define mrb_yield_with_class mrb_yield_internal #endif +#if MRUBY_RELEASE_NO < 10400 +#define MRB_PROC_SET_TARGET_CLASS(p,tc) do { \ + (p)->target_class = (tc); \ +} while (0) +#endif + +#if defined(_WIN32) || defined(_WIN64) + #include + int mkstemp(char *template) + { + DWORD pathSize; + char pathBuffer[1000]; + char tempFilename[MAX_PATH]; + UINT uniqueNum; + pathSize = GetTempPath(1000, pathBuffer); + if (pathSize < 1000) { pathBuffer[pathSize] = 0; } + else { pathBuffer[0] = 0; } + uniqueNum = GetTempFileName(pathBuffer, template, 0, tempFilename); + if (uniqueNum == 0) return -1; + strncpy(template, tempFilename, MAX_PATH); + return open(tempFilename, _O_RDWR|_O_BINARY); + } +#endif + +#ifdef USE_MRUBY_OLD_BYTE_CODE static void replace_stop_with_return(mrb_state *mrb, mrb_irep *irep) { @@ -32,6 +64,7 @@ replace_stop_with_return(mrb_state *mrb, mrb_irep *irep) irep->ilen++; } } +#endif static int compile_rb2mrb(mrb_state *mrb0, const char *code, int code_len, const char *path, FILE* tmpfp) @@ -71,9 +104,12 @@ eval_load_irep(mrb_state *mrb, mrb_irep *irep) int ai; struct RProc *proc; +#ifdef USE_MRUBY_OLD_BYTE_CODE replace_stop_with_return(mrb, irep); +#endif proc = mrb_proc_new(mrb, irep); - proc->target_class = mrb->object_class; + mrb_irep_decref(mrb, irep); + MRB_PROC_SET_TARGET_CLASS(proc, mrb->object_class); ai = mrb_gc_arena_save(mrb); mrb_yield_with_class(mrb, mrb_obj_value(proc), 0, NULL, mrb_top_self(mrb), mrb->object_class); @@ -84,7 +120,11 @@ static mrb_value mrb_require_load_rb_str(mrb_state *mrb, mrb_value self) { char *path_ptr = NULL; +#if defined(_WIN32) || defined(_WIN64) + char tmpname[MAX_PATH] = "tmp.XXXXXXXX"; +#else char tmpname[] = "tmp.XXXXXXXX"; +#endif mode_t mask; FILE *tmpfp = NULL; int fd = -1, ret; @@ -104,8 +144,9 @@ mrb_require_load_rb_str(mrb_state *mrb, mrb_value self) } umask(mask); - tmpfp = fopen(tmpname, "w+"); + tmpfp = fdopen(fd, "r+"); if (tmpfp == NULL) { + close(fd); mrb_sys_fail(mrb, "can't open temporay file at mrb_require_load_rb_str"); } diff --git a/test/load.rb b/test/load.rb new file mode 100644 index 0000000..9d04ffc --- /dev/null +++ b/test/load.rb @@ -0,0 +1,20 @@ +assert("Kernel.load") do + assert_raise(LoadError) do + load "/nonexistent.rb" + end + + $load_count = 0 + ret = Tempfile.open(["mruby-require-test", ".rb"]) { |f| + f.write <<-PROGRAM + $load_count += 1 + PROGRAM + f.flush + + load f.path + load f.path + load f.path + } + + assert_true ret + assert_equal 3, $load_count +end diff --git a/test/require.rb b/test/require.rb new file mode 100644 index 0000000..3636ea6 --- /dev/null +++ b/test/require.rb @@ -0,0 +1,100 @@ +assert("Kernel.require", "15.3.1.2.13") do + # preparation + $gvar1 = 0 + lvar1 = 0 + lvar2 = 0 + class MrubyRequireClass; end + + assert_raise(LoadError) do + require "/nonexistent.rb" + end + + ret = Tempfile.open(["mruby-require-test", ".rb"]) { |f| + f.write <<-PROGRAM + # global variables + $gvar0 = 1 + $gvar1 = 1 + + # toplevel local variables + lvar0 = 1 + lvar1 = 1 + + # can not read local variables + begin + x = lvar2 + rescue NameError => $lvar2_exc + end + + # define a procedure + def proc0 + :proc0 + end + + # define a new method of an existing class. + class MrubyRequireClass + def foo + :foo + end + end + PROGRAM + f.flush + + require(f.path) + } + + # Kernel.require returns true unless an exception is raised + assert_true ret + + # Kernel.require can create a global variable + assert_equal 1, $gvar0 + + # Kernel.require can change value of a global variable + assert_equal 1, $gvar1 + + # Kernel.require cannot create a local variable + assert_raise(NoMethodError) do + lvar0 + end + + # Kernel.require cannot change value of a local variable + assert_equal 0, lvar1 + + # Kernel.require cannot read a local variable + assert_true $lvar2_exc.is_a? NameError + + # Kernel.require can define a toplevel procedure + assert_equal :proc0, proc0 + + # Kernel.require can add a method to an existing class + # https://github.com/iij/mruby-require/issues/13 + assert_equal :foo, MrubyRequireClass.new.foo +end + +$top_self = self +assert("Kernel.require #12") do + Tempfile.open(["mruby-require-test", ".rb"]) { |f| + f.write <<-PROGRAM + $require_context = self + PROGRAM + f.flush + + require(f.path) + } + + assert_equal $top_self, $require_context +end + +assert("Kernel.require #22") do + $gvar2 = 0 + Tempfile.open(["mruby-require-test", ".rb"]) { |f| + f.write <<-PROGRAM + $gvar2 += 1 + PROGRAM + f.flush + + require(f.path) + require("/./" + f.path) + } + + assert_equal 1, $gvar2 +end diff --git a/test/test.rb b/test/test.rb deleted file mode 100644 index e3db460..0000000 --- a/test/test.rb +++ /dev/null @@ -1,32 +0,0 @@ -assert "Kernel#_load_rb_str" do - assert_equal true, self.methods.include?(:_load_rb_str) - assert_equal false, Object.const_defined?(:LOAD_RB_STR_TEST) - _load_rb_str("LOAD_RB_STR_TEST = 1") - assert_equal true, Object.const_defined?(:LOAD_RB_STR_TEST) -end - -assert "$LOAD_PATH check" do - assert_equal Array, $LOAD_PATH.class -end - -assert '$" check' do - assert_equal [], $" -end - -assert('load - error check') do - assert_raise TypeError, "load(nil) should raise TypeError" do - load nil - end - assert_raise LoadError, "load('notfound') should raise LoadError" do - load 'notfound' - end -end - -assert('require - error check') do - assert_raise TypeError, "require(nil) should raise TypeError" do - require nil - end - assert_raise LoadError, "require('notfound') should raise LoadError" do - require "notfound" - end -end diff --git a/test/test2.rb b/test/test2.rb index b66b9fd..a0a290e 100644 --- a/test/test2.rb +++ b/test/test2.rb @@ -1,4 +1,4 @@ -$dir = File.join(Dir.tmpdir, "mruby-require-test-#{Time.now.to_i}.#{Time.now.usec}") +$dir = File.join(File.realpath(Dir.tmpdir), "mruby-require-test-#{Time.now.to_i}.#{Time.now.usec}") def test_setup Dir.mkdir($dir) unless File.exist?($dir)