truck 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +26 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +14 -0
- data/lib/truck.rb +38 -0
- data/lib/truck/autoloader.rb +131 -0
- data/lib/truck/const_resolver.rb +91 -0
- data/lib/truck/context.rb +84 -0
- data/lib/truck/string_inflections.rb +30 -0
- data/lib/truck/version.rb +3 -0
- data/test/integration/autoloading_test.rb +39 -0
- data/test/support/fakes_filesystem.rb +182 -0
- data/test/support/tests_autoloading.rb +13 -0
- data/test/test_helper.rb +35 -0
- data/test/unit/autoloader_test.rb +68 -0
- data/test/unit/context_test.rb +86 -0
- data/truck.gemspec +26 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c8475ef41616dc1d5e77921e3c1a4a1c2f49e4ee
|
4
|
+
data.tar.gz: 690aa4f1b04fcbb4df44fc03da487edf43c10171
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24820f2e0c852256e6f66316070067444de7a27d7819265219840b55a898b911a2979a756b887d10125047db779d4f2224b41f04b7291b0b8762950d5007f11d
|
7
|
+
data.tar.gz: 3925f6a1600e036edb2cdbda4621dc6aef8a937b79d03bfe37bf5ae37510cafdb4223c7bda5d877d45a3427f2a1ea0dc1694a7ed53dd2efa062171f306f83792
|
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
tags
|
24
|
+
.gems
|
25
|
+
.ruby-version
|
26
|
+
.rbenv-gemsets
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nathan Ladd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Truck
|
2
|
+
|
3
|
+
Truck is an alternative autoloader that doesn't pollute the global namespace. Specifically, it does not load constants into `Object`; rather, it loads them into *Contexts* that you define. This has two main advantages:
|
4
|
+
|
5
|
+
1. `reload!` is very fast; `Object.send(:remove_const, :MyContext)` does the trick
|
6
|
+
2. You can have multiple autoloaders running in parallel contexts
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'truck'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install truck
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Unlike ActiveSupport's autoloader, truck requires a bit of setup for each context. An example:
|
25
|
+
|
26
|
+
Truck.define_context :MyContext, root: "/path/to/context/root"
|
27
|
+
|
28
|
+
Also, after defining all your contexts, you'll want to boot everyone up:
|
29
|
+
|
30
|
+
Truck.boot!
|
31
|
+
|
32
|
+
You'll want to define all your contexts, then fork/spawn threads, then have every sub process invoke `Truck.boot!` separately.
|
33
|
+
|
34
|
+
There is no notion of autoload paths; if you want multiple autoload paths, you'd define multiple contexts. In this example, a top level module called `MyContext` would get defined. Suppose you had a class called `Foo` living in `/path/to/context/root/foo.rb`:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# /path/to/context/root/foo.rb
|
38
|
+
class Foo
|
39
|
+
def self.bar
|
40
|
+
Bar.hello_world
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# /path/to/context/root/bar.rb
|
47
|
+
class Foo
|
48
|
+
def self.hello_world
|
49
|
+
"hello, world!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
`Foo` can reference `Bar` without an explicit require. So how does the world outside of `MyContext` reference objects?
|
55
|
+
|
56
|
+
MyContext.resolve_const("Bar")
|
57
|
+
|
58
|
+
This works with namespaced constant names, too:
|
59
|
+
|
60
|
+
MyContext.resolve_const("Foo::Bar::Baz")
|
61
|
+
|
62
|
+
`MyContext` has some other interesting methods on it:
|
63
|
+
|
64
|
+
# Wipe the whole context and reload it (also aliased as reload!)
|
65
|
+
MyContext.reset!
|
66
|
+
|
67
|
+
# Kill the context
|
68
|
+
MyContext.shutdown!
|
69
|
+
|
70
|
+
# Eagerly load the entire context into memory (good for production)
|
71
|
+
MyContext.eager_load!
|
72
|
+
|
73
|
+
These methods are also of course on `Truck` as well, and invoke the same operations on every context.
|
74
|
+
|
75
|
+
## Contributing
|
76
|
+
|
77
|
+
1. Fork it ( https://github.com/ntl/truck/fork )
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.pattern = 'test/**/*_test.rb'
|
6
|
+
t.libs << 'test'
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Update ctags"
|
10
|
+
task :ctags do
|
11
|
+
`ctags -R --languages=Ruby --totals -f tags`
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: 'test'
|
data/lib/truck.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative "truck/version"
|
2
|
+
require_relative "truck/string_inflections"
|
3
|
+
require_relative "truck/autoloader"
|
4
|
+
require_relative "truck/context"
|
5
|
+
require_relative "truck/const_resolver"
|
6
|
+
|
7
|
+
module Truck
|
8
|
+
extend self
|
9
|
+
|
10
|
+
attr :contexts
|
11
|
+
@contexts = {}
|
12
|
+
|
13
|
+
def define_context(name, **params)
|
14
|
+
contexts[name] = Context.new(name, **params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def boot!
|
18
|
+
contexts.each_value(&:boot!)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset!
|
22
|
+
each_booted_context &:reset!
|
23
|
+
end
|
24
|
+
alias_method :reload!, :reset!
|
25
|
+
|
26
|
+
def shutdown!
|
27
|
+
each_booted_context &:shutdown!
|
28
|
+
end
|
29
|
+
|
30
|
+
Error = Class.new StandardError
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def each_booted_context(&block)
|
35
|
+
return to_enum(:each_booted_context) unless block_given?
|
36
|
+
contexts.each_value.select(&:booted?).each(&block)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Truck
|
2
|
+
using Truck::StringInflections
|
3
|
+
|
4
|
+
class Autoloader
|
5
|
+
attr :base_nibbles, :context, :from, :dir_paths
|
6
|
+
|
7
|
+
def initialize(from)
|
8
|
+
@from = from
|
9
|
+
@context, @base_nibbles = fetch_context_and_base_nibbles
|
10
|
+
@dir_paths = [nil]
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(const_name)
|
14
|
+
raise_name_error!(const_name) unless context
|
15
|
+
@dir_paths = each_possible_const(const_name).reduce [] do |new_paths, possible_const|
|
16
|
+
resolved_const = context.resolve_const possible_const
|
17
|
+
throw :const, resolved_const if resolved_const
|
18
|
+
new_paths << possible_const if possible_namespace?(possible_const)
|
19
|
+
new_paths
|
20
|
+
end
|
21
|
+
raise_name_error!(const_name) if dir_paths.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def each_possible_const(const_name)
|
25
|
+
return to_enum(:each_possible_const, const_name) unless block_given?
|
26
|
+
dir_paths.each do |dir_path|
|
27
|
+
base_nibbles.map { |e| yield constify(e, *dir_path, const_name) }
|
28
|
+
yield constify(*dir_path, const_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def possible_namespace?(possible_const)
|
33
|
+
context.root.join(possible_const.to_snake_case).directory?
|
34
|
+
end
|
35
|
+
|
36
|
+
def constify(*nibbles)
|
37
|
+
nibbles.compact.join '::'
|
38
|
+
end
|
39
|
+
|
40
|
+
def raise_name_error!(const_name = dir_paths.last)
|
41
|
+
message = "uninitialized constant #{const_name}"
|
42
|
+
message << " (in #{context.mod})" if context
|
43
|
+
raise NameError, message
|
44
|
+
end
|
45
|
+
|
46
|
+
# given "Foo::Bar::Baz", return ["Foo::Bar::Baz", "Foo::Bar", etc.]
|
47
|
+
def fetch_context_and_base_nibbles
|
48
|
+
each_base_nibble.to_a.reverse.reduce [] do |ary, (mod, const)|
|
49
|
+
owner = Truck.contexts.each_value.detect { |c| c.mod == mod }
|
50
|
+
return [owner, ary] if owner
|
51
|
+
ary.map! do |e| e.insert 0, '::'; e.insert 0, const; end
|
52
|
+
ary << const
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# given "Foo::Bar::Baz", return ["Foo", "Bar", "Baz"]
|
58
|
+
def each_base_nibble
|
59
|
+
return to_enum(:each_base_nibble) unless block_given?
|
60
|
+
from.name.split('::').reduce Object do |mod, const|
|
61
|
+
mod = mod.const_get const
|
62
|
+
yield [mod, const]
|
63
|
+
mod
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ThreadedState
|
68
|
+
def autoloaders
|
69
|
+
@autoloaders ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def current_autoloader
|
73
|
+
autoloaders[current_thread_id]
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_current_autoloader(to:)
|
77
|
+
autoloaders[current_thread_id] = to
|
78
|
+
end
|
79
|
+
|
80
|
+
def current_thread_id
|
81
|
+
Thread.current.object_id
|
82
|
+
end
|
83
|
+
end
|
84
|
+
extend ThreadedState
|
85
|
+
|
86
|
+
module HandleConstMissing
|
87
|
+
def handle(*args)
|
88
|
+
found_const = catch :const do
|
89
|
+
handle! *args and return NullModule
|
90
|
+
end
|
91
|
+
throw :const, found_const
|
92
|
+
rescue NameError => name_error; raise name_error
|
93
|
+
ensure
|
94
|
+
set_current_autoloader(to: nil) if found_const or name_error
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle!(const_name, from:)
|
98
|
+
autoloader = current_autoloader || new(from)
|
99
|
+
autoloader << String(const_name)
|
100
|
+
set_current_autoloader to: autoloader
|
101
|
+
end
|
102
|
+
end
|
103
|
+
extend HandleConstMissing
|
104
|
+
|
105
|
+
module NullModule
|
106
|
+
extend self
|
107
|
+
|
108
|
+
def method_missing(*)
|
109
|
+
Autoloader.current_autoloader.raise_name_error!
|
110
|
+
ensure
|
111
|
+
Autoloader.set_current_autoloader to: nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Module
|
118
|
+
def const_missing(const)
|
119
|
+
catch :const do
|
120
|
+
Truck::Autoloader.handle const, from: self
|
121
|
+
end
|
122
|
+
rescue NameError => name_error
|
123
|
+
if name_error.class == NameError
|
124
|
+
# Reraise the error to keep our junk out of the backtrace
|
125
|
+
raise NameError, name_error.message
|
126
|
+
else
|
127
|
+
# NoMethodError inherits from NameError
|
128
|
+
raise name_error
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Truck
|
2
|
+
using StringInflections
|
3
|
+
|
4
|
+
class Context
|
5
|
+
class ConstResolver
|
6
|
+
attr :current_path, :autoload_paths, :context, :expanded_const
|
7
|
+
|
8
|
+
def initialize(context:, expanded_const:, autoload_paths: [''])
|
9
|
+
@autoload_paths = autoload_paths
|
10
|
+
@context = context
|
11
|
+
@expanded_const = expanded_const
|
12
|
+
end
|
13
|
+
|
14
|
+
def autoload_paths=(paths)
|
15
|
+
autoload_paths.clear
|
16
|
+
autoload_paths.concat paths
|
17
|
+
end
|
18
|
+
|
19
|
+
def const_get
|
20
|
+
walk_const_parts.reduce context.mod do |mod, const_part|
|
21
|
+
return nil unless mod.const_defined?(const_part)
|
22
|
+
mod.const_get const_part
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def each_autoload_path
|
27
|
+
autoload_paths.each do |autoload_path|
|
28
|
+
@current_path = context.root.join autoload_path
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
@current_path = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def each_possible_rb_file
|
35
|
+
each_autoload_path do
|
36
|
+
base_path = current_path.join expanded_const.to_snake_case
|
37
|
+
each_rb_file_from_base_path base_path do |rb_file|
|
38
|
+
yield rb_file if File.exist?(rb_file)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def each_rb_file_from_base_path(base_path)
|
44
|
+
base_path.ascend do |path|
|
45
|
+
return if path == context.root
|
46
|
+
rb_file = path.sub_ext '.rb'
|
47
|
+
yield rb_file if rb_file.file?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve
|
52
|
+
check_already_loaded or resolve!
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolve!
|
56
|
+
each_possible_rb_file do |rb_file|
|
57
|
+
context.load_file rb_file
|
58
|
+
check_loaded rb_file
|
59
|
+
end
|
60
|
+
const_get
|
61
|
+
end
|
62
|
+
|
63
|
+
def walk_const_parts(const = expanded_const)
|
64
|
+
return to_enum(:walk_const_parts, const) if block_given?
|
65
|
+
const.split '::'
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_already_loaded
|
69
|
+
walk_const_parts.reduce context.mod do |mod, const_part|
|
70
|
+
return nil unless mod.const_defined? const_part
|
71
|
+
mod.const_get const_part
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_loaded(rb_file)
|
76
|
+
expected_const = expected_const_defined_in_rb_file rb_file
|
77
|
+
walk_const_parts(expected_const).reduce context.mod do |mod, const_part|
|
78
|
+
mod.const_defined?(const_part) or
|
79
|
+
raise AutoloadError.new(const: expected_const, rb_file: rb_file)
|
80
|
+
mod.const_get const_part
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def expected_const_defined_in_rb_file(rb_file, autoload_path: current_path)
|
85
|
+
rel_path = rb_file.sub_ext('').relative_path_from(autoload_path).to_s
|
86
|
+
matcher = %r{\A(#{rel_path.to_camel_case})}i
|
87
|
+
expanded_const.match(matcher).captures.fetch 0
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Truck
|
2
|
+
class Context
|
3
|
+
attr :mod, :name, :root
|
4
|
+
|
5
|
+
def initialize(name, parent: nil, root:)
|
6
|
+
@mod = build_mod
|
7
|
+
@name = name
|
8
|
+
@root = Pathname(root) if root
|
9
|
+
@parent = parent
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def owner(const)
|
14
|
+
owner, _ = Autoloader.owner_and_ascending_nibbles const
|
15
|
+
owner
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def boot!
|
20
|
+
parent.const_set name, mod
|
21
|
+
end
|
22
|
+
|
23
|
+
def booted?
|
24
|
+
parent.const_defined? name
|
25
|
+
end
|
26
|
+
|
27
|
+
def eager_load!
|
28
|
+
Dir[root.join('**/*.rb')].each do |rb_file|
|
29
|
+
load_file rb_file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_file(rb_file)
|
34
|
+
mod.module_eval File.read(rb_file), rb_file.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent
|
38
|
+
return Object unless @parent
|
39
|
+
Truck.contexts.fetch(@parent.to_sym).mod
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset!
|
43
|
+
shutdown!
|
44
|
+
@mod = build_mod
|
45
|
+
boot!
|
46
|
+
end
|
47
|
+
alias_method :reload!, :reset!
|
48
|
+
|
49
|
+
def resolve_const(expanded_const)
|
50
|
+
build_const_resolver(expanded_const).resolve
|
51
|
+
end
|
52
|
+
|
53
|
+
def shutdown!
|
54
|
+
parent.send(:remove_const, name)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def build_const_resolver(expanded_const)
|
60
|
+
ConstResolver.new(
|
61
|
+
context: self,
|
62
|
+
expanded_const: String(expanded_const).dup.freeze,
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_mod
|
67
|
+
Module.new
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class AutoloadError < NameError
|
73
|
+
attr :const, :rb_file
|
74
|
+
|
75
|
+
def initialize(const:, rb_file:)
|
76
|
+
@const = const
|
77
|
+
@rb_file = rb_file
|
78
|
+
end
|
79
|
+
|
80
|
+
def message
|
81
|
+
"Expected #{rb_file} to define #{const}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Truck
|
2
|
+
module StringInflections
|
3
|
+
refine String do
|
4
|
+
def to_camel_case
|
5
|
+
str = "_#{self}"
|
6
|
+
str.gsub!(%r{_[a-z]}) { |snake| snake.slice(1).upcase }
|
7
|
+
str.gsub!('/', '::')
|
8
|
+
str
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_snake_case
|
12
|
+
str = gsub '::', '/'
|
13
|
+
# Convert FOOBar => FooBar
|
14
|
+
str.gsub! %r{[[:upper:]]{2,}} do |uppercase|
|
15
|
+
bit = uppercase[0]
|
16
|
+
bit << uppercase[1...-1].downcase
|
17
|
+
bit << uppercase[-1]
|
18
|
+
bit
|
19
|
+
end
|
20
|
+
str.gsub! %r{[[:lower:]][[:upper:]]+[[:lower:]]} do |camel|
|
21
|
+
bit = camel[0]
|
22
|
+
bit << '_'
|
23
|
+
bit << camel[1..-1].downcase
|
24
|
+
end
|
25
|
+
str.downcase!
|
26
|
+
str
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AutoloadingTest < Minitest::Test
|
4
|
+
include FakesFilesystem
|
5
|
+
include TestsAutoloading
|
6
|
+
|
7
|
+
def test_simple_case
|
8
|
+
assert_equal 'hello from A::AA', Foo::A.references_aa
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_negative_case
|
12
|
+
assert_raises NameError do
|
13
|
+
Foo::A.references_abracadabra
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_deeply_nested_module_with_implicit_namespaces
|
18
|
+
assert_equal 'hello from C::CA::CAA::CAAA', Foo::A.references_caaa
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_reference_from_within_constant
|
22
|
+
assert_equal 'hello from A', Foo::D.references_a
|
23
|
+
assert_equal 'hello from B::BA', Foo::D.new.references_b_ba
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_invoking_method_inside_implicit_namespace_raises_error
|
27
|
+
exception = assert_raises NameError do
|
28
|
+
Foo::C.hello
|
29
|
+
end
|
30
|
+
assert_equal "uninitialized constant C (in Foo)", exception.message
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_raises_name_error_if_no_context_found
|
34
|
+
exception = assert_raises NameError do
|
35
|
+
Abracadabra
|
36
|
+
end
|
37
|
+
assert_equal "uninitialized constant Abracadabra", exception.message
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module FakesFilesystem
|
2
|
+
def before_setup
|
3
|
+
super
|
4
|
+
FakeFS::FileSystem.clear
|
5
|
+
FakeFS.activate!
|
6
|
+
World.build!
|
7
|
+
monkeypatch_assert!
|
8
|
+
end
|
9
|
+
|
10
|
+
def after_teardown
|
11
|
+
unmonkeypatch_assert!
|
12
|
+
FakeFS.deactivate!
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def monkeypatch_assert!
|
19
|
+
Minitest::Assertions.module_eval do
|
20
|
+
alias_method :orig_diff, :diff
|
21
|
+
def diff(*args)
|
22
|
+
FakeFS.without { orig_diff *args }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def unmonkeypatch_assert!
|
28
|
+
Minitest::Assertions.module_eval do
|
29
|
+
undef_method :diff
|
30
|
+
alias_method :diff, :orig_diff
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module World
|
35
|
+
extend self
|
36
|
+
|
37
|
+
def build!
|
38
|
+
Dir.mkdir '/foo'
|
39
|
+
|
40
|
+
File.write "/foo/a.rb", <<-FILE
|
41
|
+
module A
|
42
|
+
def self.message
|
43
|
+
"hello from A"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.references_aa
|
47
|
+
AA.message
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.references_abracadabra
|
51
|
+
Abracadabra.message
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.references_caaa
|
55
|
+
C::CA::CAA::CAAA.message
|
56
|
+
end
|
57
|
+
|
58
|
+
module AB
|
59
|
+
def self.message
|
60
|
+
"hello from A::AB"
|
61
|
+
end
|
62
|
+
|
63
|
+
module ABA
|
64
|
+
def self.message
|
65
|
+
"hello from A::AB::ABA"
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.references_abb
|
69
|
+
ABB.message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
FILE
|
75
|
+
|
76
|
+
Dir.mkdir "/foo/a"
|
77
|
+
File.write "/foo/a/aa.rb", <<-FILE
|
78
|
+
module A
|
79
|
+
module AA
|
80
|
+
def self.message
|
81
|
+
"hello from A::AA"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
FILE
|
86
|
+
|
87
|
+
Dir.mkdir "/foo/a/ab"
|
88
|
+
File.write "/foo/a/ab/abb.rb", <<-FILE
|
89
|
+
module A
|
90
|
+
module AB
|
91
|
+
module ABB
|
92
|
+
def self.message
|
93
|
+
"hello from A::AB::ABB"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
FILE
|
99
|
+
|
100
|
+
Dir.mkdir "/foo/b"
|
101
|
+
File.write "/foo/b/ba.rb", <<-FILE
|
102
|
+
module B
|
103
|
+
module BA
|
104
|
+
def self.message
|
105
|
+
"hello from B::BA"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
FILE
|
110
|
+
|
111
|
+
Dir.mkdir "/foo/c"
|
112
|
+
Dir.mkdir "/foo/c/ca"
|
113
|
+
Dir.mkdir "/foo/c/ca/caa"
|
114
|
+
File.write "/foo/c/ca/caa/caaa.rb", <<-FILE
|
115
|
+
module C
|
116
|
+
module CA
|
117
|
+
module CAA
|
118
|
+
module CAAA
|
119
|
+
def self.message
|
120
|
+
"hello from C::CA::CAA::CAAA"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
FILE
|
127
|
+
|
128
|
+
File.write "/foo/d.rb", <<-FILE
|
129
|
+
class D
|
130
|
+
def self.references_a
|
131
|
+
A.message
|
132
|
+
end
|
133
|
+
def references_b_ba
|
134
|
+
B::BA.message
|
135
|
+
end
|
136
|
+
end
|
137
|
+
FILE
|
138
|
+
|
139
|
+
Dir.mkdir "/bar"
|
140
|
+
File.write "/bar/a.rb", <<-FILE
|
141
|
+
module A
|
142
|
+
def self.message
|
143
|
+
"hello from Bar::A"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
FILE
|
147
|
+
|
148
|
+
File.write "/bar/b.rb", <<-FILE
|
149
|
+
module B
|
150
|
+
def self.message
|
151
|
+
"hello from Bar::B"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
FILE
|
155
|
+
|
156
|
+
Dir.mkdir "/my_app"
|
157
|
+
Dir.mkdir "/my_app/lib"
|
158
|
+
File.write "/my_app/lib/a.rb", <<-FILE
|
159
|
+
module A
|
160
|
+
def self.message
|
161
|
+
"hello from MyApp::A"
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.references_b_ba
|
165
|
+
B::BA.message
|
166
|
+
end
|
167
|
+
end
|
168
|
+
FILE
|
169
|
+
|
170
|
+
Dir.mkdir "/my_app/lib/b"
|
171
|
+
File.write "/my_app/lib/b/ba.rb", <<-FILE
|
172
|
+
module B
|
173
|
+
module BA
|
174
|
+
def self.message
|
175
|
+
"hello from MyApp::B::BA"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
FILE
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module TestsAutoloading
|
2
|
+
def setup
|
3
|
+
@foo = Truck.define_context :Foo, root: "/foo"
|
4
|
+
@bar = Truck.define_context :Bar, root: '/bar', parent: :Foo
|
5
|
+
Truck.boot!
|
6
|
+
assert_nil Truck::Autoloader.current_autoloader
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
assert_nil Truck::Autoloader.current_autoloader,
|
11
|
+
"Each test in this file must clean up after itself"
|
12
|
+
end
|
13
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "minitest/reporters"
|
6
|
+
|
7
|
+
require "fakefs/safe"
|
8
|
+
require "ostruct"
|
9
|
+
require "pathname"
|
10
|
+
require "stringio"
|
11
|
+
|
12
|
+
Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
13
|
+
|
14
|
+
require_relative "../lib/truck"
|
15
|
+
|
16
|
+
$LOAD_PATH << "test/support"
|
17
|
+
|
18
|
+
Minitest::Test.class_eval do
|
19
|
+
autoload :FakesFilesystem, "fakes_filesystem"
|
20
|
+
autoload :TestsAutoloading, "tests_autoloading"
|
21
|
+
|
22
|
+
def after_teardown
|
23
|
+
Truck.shutdown!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Binding
|
28
|
+
def method_missing(sym, *)
|
29
|
+
return super unless sym == :pry
|
30
|
+
FakeFS.without do
|
31
|
+
require 'pry'
|
32
|
+
pry
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AutoloaderTest < Minitest::Test
|
4
|
+
include FakesFilesystem
|
5
|
+
include TestsAutoloading
|
6
|
+
|
7
|
+
def test_throws_constant_when_found
|
8
|
+
const = assert_catches :const do
|
9
|
+
Truck::Autoloader.handle :A, from: Foo
|
10
|
+
end
|
11
|
+
assert_equal 'Foo::A', const.name
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_raises_error_when_const_not_found
|
15
|
+
exception = assert_raises NameError do
|
16
|
+
Truck::Autoloader.handle :Abracadabra, from: Foo
|
17
|
+
end
|
18
|
+
assert_equal 'uninitialized constant Abracadabra (in Foo)', exception.message
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_simple_implicit_namespace_case
|
22
|
+
Truck::Autoloader.handle :B, from: Foo
|
23
|
+
const = assert_catches :const do
|
24
|
+
Truck::Autoloader.handle :BA, from: Foo
|
25
|
+
end
|
26
|
+
assert_equal 'Foo::B::BA', const.name
|
27
|
+
assert_nil Truck::Autoloader.current_autoloader
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_cleanup_after_implicit_namespace
|
31
|
+
Truck::Autoloader.handle :B, from: Foo
|
32
|
+
assert_raises NameError do
|
33
|
+
Truck::Autoloader.handle :Abracadabra, from: Foo
|
34
|
+
end
|
35
|
+
assert_nil Truck::Autoloader.current_autoloader
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_deeply_nested_module_with_implicit_namespaces
|
39
|
+
%i(C CA CAA).each do |implicit_namespace|
|
40
|
+
Truck::Autoloader.handle implicit_namespace, from: Foo
|
41
|
+
end
|
42
|
+
const = assert_catches :const do
|
43
|
+
Truck::Autoloader.handle :CAAA, from: Foo
|
44
|
+
end
|
45
|
+
assert_equal 'Foo::C::CA::CAA::CAAA', const.name
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_shallowly_nested_module_with_implicit_namespaces
|
49
|
+
@foo.resolve_const 'A::AB::ABA'
|
50
|
+
|
51
|
+
const = assert_catches :const do
|
52
|
+
Truck::Autoloader.handle :ABB, from: Foo::A::AB::ABA
|
53
|
+
end
|
54
|
+
assert_equal 'Foo::A::AB::ABB', const.name
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def assert_catches(thrown)
|
60
|
+
val = catch thrown do
|
61
|
+
yield
|
62
|
+
:not_found
|
63
|
+
end
|
64
|
+
refute_equal :not_found, val, "Expected block to throw #{thrown.inspect}"
|
65
|
+
val
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ContextTest < Minitest::Test
|
4
|
+
include FakesFilesystem
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@context = Truck.define_context :Foo, root: "/foo"
|
8
|
+
@context.boot!
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_resolving_constant
|
12
|
+
mod = @context.resolve_const(:A)
|
13
|
+
assert_autoloaded_module 'A', mod
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_resolving_deep_constant
|
17
|
+
mod = @context.resolve_const('A::AA')
|
18
|
+
assert_autoloaded_module 'A::AA', mod
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_resolving_inner_constant
|
22
|
+
mod = @context.resolve_const('A::AB::ABA')
|
23
|
+
assert_autoloaded_module 'A::AB::ABA', mod
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_parent_namespace_defined_in_child
|
27
|
+
mod = @context.resolve_const('B::BA')
|
28
|
+
assert_autoloaded_module 'B::BA', mod
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_returns_nil_if_constant_isnt_defined
|
32
|
+
assert_nil @context.resolve_const(:Abracadabra)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_returns_constant_that_has_already_been_resolved
|
36
|
+
@context.resolve_const(:A)
|
37
|
+
File.write '/foo/a.rb', "raise 'should not load this file'"
|
38
|
+
@context.resolve_const(:A)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_raises_error_if_corresponding_file_did_not_define_constant
|
42
|
+
File.write "/foo/abracadabra.rb", ""
|
43
|
+
|
44
|
+
exception = assert_raises(Truck::AutoloadError) do
|
45
|
+
@context.resolve_const(:Abracadabra)
|
46
|
+
end
|
47
|
+
|
48
|
+
expected_message = %r{Expected /foo/abracadabra\.rb to define Abracadabra}
|
49
|
+
assert_match expected_message, exception.message
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_nesting_context
|
53
|
+
nested_context = Truck.define_context :Bar, root: "/bar", parent: 'Foo'
|
54
|
+
nested_context.boot!
|
55
|
+
|
56
|
+
mod = nested_context.resolve_const('A')
|
57
|
+
assert_autoloaded_module 'Bar::A', mod
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_eager_load
|
61
|
+
refute Foo.const_defined?(:A)
|
62
|
+
@context.eager_load!
|
63
|
+
assert Foo.const_defined?(:A)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_reload_drops_constant_references
|
67
|
+
@context.eager_load!
|
68
|
+
assert Foo.const_defined?(:A)
|
69
|
+
@context.reload!
|
70
|
+
refute Foo.const_defined?(:A)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_shutdown_removes_const
|
74
|
+
assert @context.booted?
|
75
|
+
@context.shutdown!
|
76
|
+
refute @context.booted?
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def assert_autoloaded_module(expected_name, mod)
|
82
|
+
assert_kind_of Module, mod
|
83
|
+
assert_equal "Foo::#{expected_name}", mod.name
|
84
|
+
assert_equal "hello from #{expected_name}", mod.message
|
85
|
+
end
|
86
|
+
end
|
data/truck.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'truck/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "truck"
|
8
|
+
spec.version = Truck::VERSION
|
9
|
+
spec.authors = ["ntl"]
|
10
|
+
spec.email = ["nathanladd+github@gmail.com"]
|
11
|
+
spec.summary = %q{Truck is an alternative autoloader that doesn't pollute the global namespace. Specifically, it does not load constants into `Object`; rather, it loads them into *Contexts* that you define.}
|
12
|
+
spec.description = %q{Truck is an alternative autoloader that doesn't pollute the global namespace.}
|
13
|
+
spec.homepage = "https://github.com/ntl/truck"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "fakefs", "~> 0.5"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
spec.add_development_dependency "minitest-reporters", "~> 1.0"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: truck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ntl
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fakefs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: Truck is an alternative autoloader that doesn't pollute the global namespace.
|
84
|
+
email:
|
85
|
+
- nathanladd+github@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/truck.rb
|
96
|
+
- lib/truck/autoloader.rb
|
97
|
+
- lib/truck/const_resolver.rb
|
98
|
+
- lib/truck/context.rb
|
99
|
+
- lib/truck/string_inflections.rb
|
100
|
+
- lib/truck/version.rb
|
101
|
+
- test/integration/autoloading_test.rb
|
102
|
+
- test/support/fakes_filesystem.rb
|
103
|
+
- test/support/tests_autoloading.rb
|
104
|
+
- test/test_helper.rb
|
105
|
+
- test/unit/autoloader_test.rb
|
106
|
+
- test/unit/context_test.rb
|
107
|
+
- truck.gemspec
|
108
|
+
homepage: https://github.com/ntl/truck
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.2.2
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Truck is an alternative autoloader that doesn't pollute the global namespace.
|
132
|
+
Specifically, it does not load constants into `Object`; rather, it loads them into
|
133
|
+
*Contexts* that you define.
|
134
|
+
test_files:
|
135
|
+
- test/integration/autoloading_test.rb
|
136
|
+
- test/support/fakes_filesystem.rb
|
137
|
+
- test/support/tests_autoloading.rb
|
138
|
+
- test/test_helper.rb
|
139
|
+
- test/unit/autoloader_test.rb
|
140
|
+
- test/unit/context_test.rb
|