chronos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +27 -0
- data/HISTORY.rdoc +4 -0
- data/LICENSE.txt +52 -0
- data/MANIFEST.txt +51 -0
- data/NOTES.rdoc +85 -0
- data/README.rdoc +125 -0
- data/Rakefile +34 -0
- data/TODO.rdoc +63 -0
- data/bench/completebench.rb +24 -0
- data/ext/cchronos/extconf.rb +5 -0
- data/ext/chronos_core/extconf.rb +5 -0
- data/lib/chronos.rb +208 -0
- data/lib/chronos/calendar.rb +16 -0
- data/lib/chronos/calendar/gregorian.rb +94 -0
- data/lib/chronos/data/zones.tab +424 -0
- data/lib/chronos/datetime.rb +299 -0
- data/lib/chronos/datetime/gregorian.rb +698 -0
- data/lib/chronos/duration.rb +141 -0
- data/lib/chronos/duration/gregorian.rb +261 -0
- data/lib/chronos/durationtotext.rb +42 -0
- data/lib/chronos/exceptions.rb +16 -0
- data/lib/chronos/gregorian.rb +27 -0
- data/lib/chronos/interval.rb +132 -0
- data/lib/chronos/interval/gregorian.rb +80 -0
- data/lib/chronos/locale/parsers/de_CH.rb +50 -0
- data/lib/chronos/locale/parsers/en_US.rb +1 -0
- data/lib/chronos/locale/parsers/generic.rb +21 -0
- data/lib/chronos/locale/strings/de_DE.yaml +76 -0
- data/lib/chronos/locale/strings/en_US.yaml +76 -0
- data/lib/chronos/minimalistic.rb +37 -0
- data/lib/chronos/numeric/gregorian.rb +100 -0
- data/lib/chronos/ruby.rb +6 -0
- data/lib/chronos/version.rb +21 -0
- data/lib/chronos/zone.rb +212 -0
- data/rake/initialize.rb +116 -0
- data/rake/lib/assesscode.rb +59 -0
- data/rake/lib/bonesplitter.rb +245 -0
- data/rake/lib/projectclass.rb +69 -0
- data/rake/tasks/copyright.rake +24 -0
- data/rake/tasks/gem.rake +119 -0
- data/rake/tasks/git.rake +40 -0
- data/rake/tasks/loc.rake +33 -0
- data/rake/tasks/manifest.rake +63 -0
- data/rake/tasks/meta.rake +16 -0
- data/rake/tasks/notes.rake +36 -0
- data/rake/tasks/post_load.rake +18 -0
- data/rake/tasks/rdoc.rake +73 -0
- data/rake/tasks/rubyforge.rake +67 -0
- data/rake/tasks/spec.rake +55 -0
- data/spec/bacon_helper.rb +43 -0
- data/spec/lib/chronos/datetime/gregorian_spec.rb +314 -0
- data/spec/lib/chronos/datetime_spec.rb +219 -0
- data/spec/lib/chronos_spec.rb +91 -0
- metadata +111 -0
data/rake/tasks/git.rake
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :git do
|
10
|
+
if Project.meta.use_git then
|
11
|
+
# A prerequisites task that all other tasks depend upon
|
12
|
+
task :prereqs
|
13
|
+
|
14
|
+
desc 'Show tags from the Git repository'
|
15
|
+
task :tags => 'git:prereqs' do |t|
|
16
|
+
system 'git', 'tag'
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Create a new tag in the Git repository'
|
20
|
+
task :create_tag => 'git:prereqs' do |t|
|
21
|
+
v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
|
22
|
+
|
23
|
+
tag = "%s-%s" % [Project.meta.name, Project.meta.version]
|
24
|
+
msg = "Creating tag for #{Project.meta.name} version #{Project.meta.version}"
|
25
|
+
|
26
|
+
puts "Creating Git tag '#{tag}'"
|
27
|
+
unless system "git tag -a -m '#{msg}' #{tag}"
|
28
|
+
abort "Tag creation failed"
|
29
|
+
end
|
30
|
+
|
31
|
+
if %x/git remote/ =~ %r/^origin\s*$/
|
32
|
+
unless system "git push origin #{tag}"
|
33
|
+
abort "Could not push tag to remote Git repository"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end # namespace :git
|
39
|
+
|
40
|
+
task 'gem:release' => 'git:create_tag' if Project.meta.use_git
|
data/rake/tasks/loc.rake
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :loc do
|
10
|
+
desc 'Assess the number of code and comment lines'
|
11
|
+
task :assess do
|
12
|
+
next unless lib?('assesscode', 'Requires AssessCode lib to count lines of code and comment.')
|
13
|
+
a = AssessCode.new(
|
14
|
+
'.',
|
15
|
+
'lib/**/*.rb',
|
16
|
+
'bin/**/*',
|
17
|
+
'data/**/*.rb'
|
18
|
+
)
|
19
|
+
puts "Code"
|
20
|
+
a.put_assessment
|
21
|
+
|
22
|
+
a = AssessCode.new(
|
23
|
+
'.',
|
24
|
+
'spec/**/*.rb',
|
25
|
+
'test/**/*.rb'
|
26
|
+
)
|
27
|
+
puts "\nTests"
|
28
|
+
a.put_assessment
|
29
|
+
end
|
30
|
+
end # namespace :loc
|
31
|
+
|
32
|
+
desc 'Alias to loc:assess'
|
33
|
+
task :loc => 'loc:assess'
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :manifest do
|
10
|
+
# prerequisite task finalizes Project.manifest
|
11
|
+
task :prerequisite => 'meta:prerequisite' do
|
12
|
+
abort("No instructions for manifest tasks in Project") unless Project.manifest
|
13
|
+
|
14
|
+
Project.manifest.file ||= Project.meta.manifest
|
15
|
+
Project.manifest.__finalize__
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Verify the manifest'
|
19
|
+
task :check => :prerequisite do
|
20
|
+
files = manifest()
|
21
|
+
cands = manifest_candidates()
|
22
|
+
missing = files-cands
|
23
|
+
added = cands-files
|
24
|
+
|
25
|
+
puts "#{Project.manifest.file.inspect}:"
|
26
|
+
puts added.sort.map { |f|
|
27
|
+
"\e[32m+#{f}\e[0m"
|
28
|
+
}
|
29
|
+
puts missing.sort.map { |f|
|
30
|
+
"\e[31m-#{f}\e[0m"
|
31
|
+
}
|
32
|
+
puts "Manifest is up to date." if missing.empty? and added.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Create a new manifest'
|
36
|
+
task :create => :prerequisite do
|
37
|
+
abort("No manifest file path given in Project.manifest.file") unless Project.manifest.file
|
38
|
+
|
39
|
+
unless File.exist?(Project.manifest.file) then
|
40
|
+
files = manifest_candidates()+[Project.manifest.file]
|
41
|
+
files.sort!
|
42
|
+
File.open(Project.manifest.file, 'wb') {|fp| fp.puts files}
|
43
|
+
puts "#{Project.manifest.file.inspect} Created."
|
44
|
+
else
|
45
|
+
abort("#{Project.manifest.file.inspect} exists already.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :assert => :prerequisite do
|
50
|
+
files = manifest()
|
51
|
+
cands = manifest_candidates()
|
52
|
+
missing = files-cands
|
53
|
+
added = cands-files
|
54
|
+
|
55
|
+
unless (missing.empty? and added.empty?)
|
56
|
+
raise "ERROR: #{Project.manifest.file.inspect} is out of date"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # namespace :manifest
|
61
|
+
|
62
|
+
desc 'Alias to manifest:check'
|
63
|
+
task :manifest => 'manifest:check'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :meta do
|
10
|
+
task :prerequisite do
|
11
|
+
Project.manifest.__finalize__
|
12
|
+
end
|
13
|
+
end # namespace :meta
|
14
|
+
|
15
|
+
#desc 'Alias to manifest:check'
|
16
|
+
#task :manifest => 'manifest:check'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :notes do
|
10
|
+
desc "Show all annotations"
|
11
|
+
task :show, :tags do |t, args|
|
12
|
+
tags = if args.tags then
|
13
|
+
args.tags.split(/,\s*/)
|
14
|
+
else
|
15
|
+
Project.notes.tags
|
16
|
+
end
|
17
|
+
regex = /^.*(?:#{tags.map { |e| Regexp.escape(e) }.join('|')}).*$/
|
18
|
+
puts "Searching for tags #{tags.join(', ')}"
|
19
|
+
Project.notes.include.each { |glob|
|
20
|
+
Dir.glob(glob) { |file|
|
21
|
+
data = File.read(file)
|
22
|
+
header = false
|
23
|
+
data.scan(regex) {
|
24
|
+
unless header then
|
25
|
+
puts "#{file}:"
|
26
|
+
header = true
|
27
|
+
end
|
28
|
+
printf "- %4d: %s\n", $`.count("\n")+1, $&.strip
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end # namespace :notes
|
34
|
+
|
35
|
+
desc "Alias for notes:show. You have to use notes:show directly to use arguments."
|
36
|
+
task :notes => 'notes:show'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# This rakefile doesn't define any tasks, it is run after Rakefile has run and before
|
10
|
+
# any other imported rakefile, so it can clean up the Project object and resolve some
|
11
|
+
# dependencies.
|
12
|
+
|
13
|
+
|
14
|
+
# defaultize meta data, have to do this here because many tasks depend on Project.meta
|
15
|
+
# for initialization and task creation.
|
16
|
+
Project.meta.summary ||= proc { extract_summary() }
|
17
|
+
Project.meta.description ||= proc { extract_description() || extract_summary() }
|
18
|
+
Project.meta.__finalize__
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'rake/rdoctask'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
namespace :doc do
|
14
|
+
if Project.rdoc.nil? then
|
15
|
+
task :missing_project do
|
16
|
+
abort("No instructions in Project to run the gem tasks")
|
17
|
+
end
|
18
|
+
|
19
|
+
task :rdoc => :missing_project
|
20
|
+
task :clobber_rdoc => :missing_project
|
21
|
+
task :coverage => :missing_project
|
22
|
+
task :ri => :missing_project
|
23
|
+
task :clobber_ri => :missing_project
|
24
|
+
elsif !lib?('rake/rdoctask') then
|
25
|
+
task :missing_rdoctask do
|
26
|
+
abort("Missing rake/rdoctask in Project to run the gem tasks")
|
27
|
+
end
|
28
|
+
|
29
|
+
task :rdoc => :missing_rdoctask
|
30
|
+
task :clobber_rdoc => :missing_rdoctask
|
31
|
+
task :coverage => :missing_rdoctask
|
32
|
+
task :ri => :missing_rdoctask
|
33
|
+
task :clobber_ri => :missing_rdoctask
|
34
|
+
else
|
35
|
+
# defaultize rdoc task
|
36
|
+
Project.rdoc.files ||= []
|
37
|
+
Project.rdoc.files += FileList.new(Project.rdoc.include || %w[lib/**/* *.{txt markdown rdoc}])
|
38
|
+
Project.rdoc.files -= FileList.new(Project.rdoc.exclude) if Project.rdoc.exclude
|
39
|
+
Project.rdoc.files.reject! { |f| File.directory?(f) }
|
40
|
+
Project.rdoc.title ||= "#{Project.meta.name}-#{Project.meta.version} Documentation"
|
41
|
+
Project.rdoc.options ||= []
|
42
|
+
Project.rdoc.options.push('-t', Project.rdoc.title)
|
43
|
+
Project.rdoc.main ||= Project.meta.readme
|
44
|
+
Project.rdoc.__finalize__
|
45
|
+
|
46
|
+
Rake::RDocTask.new do |rd|
|
47
|
+
rd.main = Project.rdoc.main
|
48
|
+
rd.rdoc_files = Project.rdoc.files
|
49
|
+
rd.rdoc_dir = Project.rdoc.output_dir
|
50
|
+
rd.template = Project.rdoc.template if Project.rdoc.template
|
51
|
+
|
52
|
+
rd.options.concat(Project.rdoc.options)
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'Check documentation coverage with dcov'
|
56
|
+
task :coverage do
|
57
|
+
sh "find lib -name '*.rb' | xargs dcov"
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'Generate ri locally for testing'
|
61
|
+
task :ri => :clobber_ri do
|
62
|
+
sh "#{RDOC} --ri -o ri ."
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Remove ri products'
|
66
|
+
task :clobber_ri do
|
67
|
+
rm_r 'ri' rescue nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
desc 'Alias to doc:rdoc'
|
73
|
+
task :doc => 'doc:rdoc'
|
@@ -0,0 +1,67 @@
|
|
1
|
+
namespace :gem do
|
2
|
+
# Empty tasks for missing prerequisites.
|
3
|
+
if !lib?(%w'rubyforge rake/contrib/sshpublisher') then
|
4
|
+
task :missing_prerequisites do
|
5
|
+
'This task requires rubyforge and/or rake/contrib/sshpublisher to run.'
|
6
|
+
end
|
7
|
+
|
8
|
+
task :release => :missing_prerequisites
|
9
|
+
task :release_notes => :missing_prerequisites
|
10
|
+
task :release_changes => :missing_prerequisites
|
11
|
+
task :preformatted => :missing_prerequisites
|
12
|
+
|
13
|
+
# Real tasks if prerequisites are met.
|
14
|
+
else
|
15
|
+
Project.rubyforge.name ||= Project.meta.name
|
16
|
+
Project.rubyforge.description ||= Project.meta.description
|
17
|
+
Project.rubyforge.changes ||= Project.meta.changes
|
18
|
+
Project.rubyforge.version = Project.meta.version
|
19
|
+
|
20
|
+
desc 'Package and upload to RubyForge'
|
21
|
+
task :release => [:clobber, 'gem:package'] do |t|
|
22
|
+
v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
|
23
|
+
abort "Versions don't match #{v} vs #{PROJ.version}" if v != Project.meta.version.to_s
|
24
|
+
pkg = "pkg/#{Project.gem.spec.full_name}"
|
25
|
+
|
26
|
+
if $DEBUG then
|
27
|
+
puts "release_id = rf.add_release #{Project.rubyforge.name.inspect}, #{Project.rubyforge.name.inspect}, #{Project.rubyforge.version.inspect}, \"#{pkg}.tgz\""
|
28
|
+
puts "rf.add_file #{Project.rubyforge.name.inspect}, #{Project.meta.name.inspect}, release_id, \"#{pkg}.gem\""
|
29
|
+
end
|
30
|
+
|
31
|
+
rf = RubyForge.new
|
32
|
+
puts 'Logging in'
|
33
|
+
rf.login
|
34
|
+
|
35
|
+
c = rf.userconfig
|
36
|
+
c['release_notes'] = Project.rubyforge.description if Project.rubyforge.description
|
37
|
+
c['release_changes'] = Project.rubyforge.changes if Project.rubyforge.changes
|
38
|
+
c['preformatted'] = true
|
39
|
+
|
40
|
+
files = [(Project.gem.need_tar ? "#{pkg}.tgz" : nil),
|
41
|
+
(Project.gem.need_zip ? "#{pkg}.zip" : nil),
|
42
|
+
"#{pkg}.gem"].compact
|
43
|
+
|
44
|
+
puts "Releasing #{Project.rubyforge.name} v. #{Project.rubyforge.version}"
|
45
|
+
rf.add_release Project.rubyforge.name, Project.meta.name, Project.rubyforge.version, *files
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end # namespace :gem
|
49
|
+
|
50
|
+
|
51
|
+
namespace :doc do
|
52
|
+
if !lib?(%w'rubyforge rake/contrib/sshpublisher') then
|
53
|
+
desc "Publish RDoc to RubyForge"
|
54
|
+
task :release => 'rubyforge:missing_prerequisite'
|
55
|
+
else
|
56
|
+
desc "Publish RDoc to RubyForge"
|
57
|
+
task :release => %w(doc:clobber_rdoc doc:rdoc) do
|
58
|
+
config = YAML.load_file(File.expand_path('~/.rubyforge/user-config.yml'))
|
59
|
+
host = "#{config['username']}@rubyforge.org"
|
60
|
+
remote_dir = "/var/www/gforge-projects/#{Project.rubyforge.name}/"
|
61
|
+
remote_dir << Project.rdoc.remote_dir if Project.rdoc.remote_dir
|
62
|
+
local_dir = Project.rdoc.dir
|
63
|
+
|
64
|
+
Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # namespace :doc
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007-2008 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
namespace :spec do
|
10
|
+
desc 'Run all specs with basic output'
|
11
|
+
task :run do
|
12
|
+
dependency(%w'bacon flexmock', 'Requires %s to run')
|
13
|
+
|
14
|
+
Bacon.extend Bacon.const_get('TestUnitOutput') rescue abort "No such formatter: 'TestUnitOutput'"
|
15
|
+
Bacon.summary_on_exit
|
16
|
+
|
17
|
+
Dir.glob("spec/**/*_spec.rb") { |file|
|
18
|
+
load file
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Run all specs with text output'
|
23
|
+
task :specdoc do |t|
|
24
|
+
raise "Not implemented"
|
25
|
+
end
|
26
|
+
|
27
|
+
optional_task(:rcov, 'Spec::Rake::SpecTask') do
|
28
|
+
desc 'Run all specs with RCov'
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
30
|
+
t.ruby_opts = Project.rcov.ruby_opts
|
31
|
+
t.spec_opts = Project.rcov.opts
|
32
|
+
t.spec_files = Project.rcov.files
|
33
|
+
t.libs = Project.rcov.libs || []
|
34
|
+
t.rcov = true
|
35
|
+
t.rcov_dir = Project.rcov.dir
|
36
|
+
t.rcov_opts = Project.rcov.opts
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
optional_task(:verify, 'Rcov::VerifyTask') do
|
41
|
+
Rcov::VerifyTask.new(:verify) do |t|
|
42
|
+
t.threshold = Project.rcov.threshold
|
43
|
+
t.index_html = File.join(Project.rcov.dir, 'index.html')
|
44
|
+
t.require_exact_threshold = Project.rcov.threshold_exact
|
45
|
+
end
|
46
|
+
|
47
|
+
task :verify => :rcov
|
48
|
+
end
|
49
|
+
|
50
|
+
end # namespace :spec
|
51
|
+
|
52
|
+
desc 'Alias to spec:run'
|
53
|
+
task :spec => 'spec:run'
|
54
|
+
|
55
|
+
task :clobber => 'spec:clobber'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'bacon', '>= 1.0.0'
|
4
|
+
rescue LoadError
|
5
|
+
warn "Running without rubygems. Make sure bacon is at least 1.0.0"
|
6
|
+
end
|
7
|
+
begin require 'test/unit'; rescue LoadError; end
|
8
|
+
require 'bacon'
|
9
|
+
require 'flexmock'
|
10
|
+
|
11
|
+
base = File.expand_path(File.dirname(__FILE__)+'/..')
|
12
|
+
$LOAD_PATH.unshift(base+'/lib') unless $LOAD_PATH.include?(base+'/lib')
|
13
|
+
$LOAD_PATH.unshift(base+'/spec') unless $LOAD_PATH.include?(base+'/spec')
|
14
|
+
|
15
|
+
include FlexMock::MockContainer
|
16
|
+
|
17
|
+
Test::Unit.run = false
|
18
|
+
|
19
|
+
def equal_unordered(expected)
|
20
|
+
lambda { |actual|
|
21
|
+
seen = Hash.new(0)
|
22
|
+
expected.each { |e| seen[e] += 1 }
|
23
|
+
actual.each { |e| seen[e] -= 1 }
|
24
|
+
seen.invert.keys == [0]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def iterate(expected, meth=:each, *args)
|
29
|
+
lambda { |obj|
|
30
|
+
t = []
|
31
|
+
obj.__send__(meth, *args) { |v| t << v }
|
32
|
+
t == expected
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def iterate_unordered(expected, meth=:each, *args)
|
37
|
+
lambda { |obj|
|
38
|
+
seen = Hash.new(0)
|
39
|
+
expected.each { |e| seen[e] += 1 }
|
40
|
+
obj.__send__(meth, *args) { |e| seen[e] -= 1 }
|
41
|
+
seen.invert.keys == [0]
|
42
|
+
}
|
43
|
+
end
|