├── VERSION ├── lib ├── lemon.yml ├── lemon │ ├── ae.rb │ ├── core_ext │ │ ├── module.rb │ │ └── kernel.rb │ ├── core_ext.rb │ ├── test_scope.rb │ ├── coverage │ │ ├── formats │ │ │ ├── yaml.rb │ │ │ ├── compact.rb │ │ │ ├── abstract.rb │ │ │ ├── outline.rb │ │ │ └── verbose.rb │ │ ├── cover_unit.rb │ │ ├── snapshot.rb │ │ ├── generator.rb │ │ ├── source_parser.rb │ │ └── analyzer.rb │ ├── test_world.rb │ ├── ignore_callers.rb │ ├── cli │ │ ├── obrother.rb │ │ ├── coverage.rb │ │ ├── test.rb │ │ ├── generate.rb │ │ ├── scaffold.rb │ │ ├── base.rb │ │ └── lemon.ascii │ ├── test_class.rb │ ├── test_advice.rb │ ├── cli.rb │ ├── test_setup.rb │ ├── test_class_method.rb │ ├── test_module.rb │ ├── test_proc.rb │ ├── test_method.rb │ └── test_case.rb └── lemon.rb ├── demo ├── applique │ ├── ae.rb │ └── fs.rb └── coverage │ ├── applique │ └── lemon.rb │ ├── 03_extensions.md │ ├── 02_incomplete.md │ └── 01_complete.md ├── .ruby ├── work ├── deprecated │ ├── features │ │ ├── support │ │ │ ├── ae.rb │ │ │ └── aruba.rb │ │ ├── step_definitions │ │ │ └── coverage_steps.rb │ │ ├── coverage.feature │ │ ├── test.feature │ │ └── generate.feature │ ├── test │ │ ├── runner │ │ ├── fixtures │ │ │ ├── case_incomplete.rb │ │ │ ├── example.rb │ │ │ ├── helper.rb │ │ │ ├── case_inclusion.rb │ │ │ └── case_complete.rb │ │ ├── case_coverage_analyzer.rb │ │ └── case_test_case_dsl.rb │ ├── cucumber.yml │ ├── command │ │ ├── abstract.rb │ │ ├── test.rb │ │ ├── coverage.rb │ │ └── generate.rb │ ├── model │ │ ├── dsl │ │ │ ├── subject.rb │ │ │ └── advice.rb │ │ ├── test.rb │ │ ├── test_context.rb │ │ ├── test_base_dsl.rb │ │ ├── main.rb │ │ ├── test_clause.rb │ │ ├── test_feature.rb │ │ ├── test_scenario.rb │ │ └── test_suite.rb │ └── rake.rb ├── old-tests │ ├── case_example.rb │ └── feature_example.rb └── reference │ ├── dynamic_constant_lookup.rb │ └── dsl2.rb ├── Gemfile ├── .yardopts ├── try ├── helpers │ └── loadpath.rb ├── fixtures │ ├── example-use.rb │ ├── calculator.rb │ └── example.rb ├── .test ├── case_untested.rb ├── case_pending.rb ├── case_scope.rb ├── case_singleton.rb ├── case_error.rb ├── case_fail.rb └── case_pass.rb ├── .gitignore ├── .travis.yml ├── Config.rb ├── bin └── lemons ├── Reapfile ├── Assembly ├── Indexfile ├── .index ├── LICENSE.txt ├── MANIFEST.txt ├── HISTORY.md ├── pkg └── lemon.gemspec └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.9.1 2 | -------------------------------------------------------------------------------- /lib/lemon.yml: -------------------------------------------------------------------------------- 1 | ../.ruby -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | lemon 0.9.1 2012-03-09 2 | lib 3 | 4 | -------------------------------------------------------------------------------- /work/deprecated/features/support/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /work/deprecated/features/support/aruba.rb: -------------------------------------------------------------------------------- 1 | require 'aruba' 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec :path=>'pkg' 3 | -------------------------------------------------------------------------------- /work/deprecated/features/step_definitions/coverage_steps.rb: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/coverage/applique/lemon.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/coverage/analyzer' 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --output-dir doc 2 | --private 3 | --protected 4 | lib/ 5 | - 6 | [A-Z][A-Z]*.* 7 | -------------------------------------------------------------------------------- /work/deprecated/test/runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | system 'lemon -Ilib -v test/*.rb' 3 | -------------------------------------------------------------------------------- /try/helpers/loadpath.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path(File.dirname(__FILE__) + '/../fixtures') 2 | -------------------------------------------------------------------------------- /lib/lemon/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | require 'ae/expect' 3 | require 'ae/should' 4 | require 'ae/pry' 5 | -------------------------------------------------------------------------------- /try/fixtures/example-use.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/example.rb' 2 | 3 | ex = Example.new 4 | ex.f(1,2) 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | doc 4 | log 5 | pkg/*.gem 6 | pkg/*.gz 7 | tmp 8 | DEMO* 9 | web 10 | work/sandbox 11 | -------------------------------------------------------------------------------- /try/.test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | Test.run :default do |r| 4 | require 'ae' 5 | require 'lemon' 6 | $:.unshift 'fixtures' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/lemon/core_ext/module.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | 3 | # 4 | def namespace 5 | i = name.rindex('::') 6 | i ? name[0...i] : name 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /work/deprecated/cucumber.yml: -------------------------------------------------------------------------------- 1 | default: features 2 | profile: --format profile features 3 | log: --format progress --format html --out=log/cucumber/index.html features 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | #- rbx 8 | #- rbx-19mode 9 | - jruby 10 | - jruby-19mode 11 | - ree 12 | -------------------------------------------------------------------------------- /lib/lemon/core_ext.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION > '1.9' 2 | require_relative 'core_ext/kernel' 3 | require_relative 'core_ext/module' 4 | else 5 | require 'lemon/core_ext/kernel' 6 | require 'lemon/core_ext/module' 7 | end 8 | -------------------------------------------------------------------------------- /work/deprecated/test/fixtures/case_incomplete.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | testcase X do 4 | 5 | unit :a => "Returns a String" do 6 | end 7 | 8 | unit :b => "Returns a String" do 9 | end 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib/lemon/test_scope.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | class TestScope < World 4 | 5 | # 6 | def initialize(testcase) 7 | @_testcase = testcase 8 | 9 | extend testcase.domain 10 | end 11 | 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /try/case_untested.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | test "nothing doing" do 8 | # notice Example#f has not been called 9 | end 10 | 11 | end 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /work/deprecated/test/fixtures/example.rb: -------------------------------------------------------------------------------- 1 | class X 2 | def a; "a"; end 3 | def b; "b"; end 4 | def c; "c"; end 5 | end 6 | class Y 7 | def q; "q"; end 8 | protected 9 | def r; "r"; end 10 | private 11 | def s; "s"; end 12 | end 13 | 14 | -------------------------------------------------------------------------------- /work/deprecated/test/fixtures/helper.rb: -------------------------------------------------------------------------------- 1 | # Some fixture code for testing #include. 2 | module HelperMixin 3 | 4 | def help; "help"; end 5 | 6 | module SubModule 7 | 8 | def self.help; "sub-help"; end 9 | 10 | end 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /lib/lemon/coverage/formats/yaml.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/coverage/formats/abstract' 2 | 3 | module Lemon::CoverReports 4 | 5 | class Yaml < Abstract 6 | 7 | # 8 | def render 9 | puts checklist.to_yaml 10 | end 11 | 12 | end 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /try/fixtures/calculator.rb: -------------------------------------------------------------------------------- 1 | class Calculator 2 | 3 | def initialize 4 | @stack = [] 5 | end 6 | 7 | def push(int) 8 | @stack << int 9 | end 10 | 11 | def add 12 | @stack.inject(0){ |sum, val| sum += val; sum } 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /Config.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | profile :coverage do 4 | 5 | config :qed do 6 | require 'simplecov' 7 | SimpleCov.start do 8 | coverage_dir 'log/coverage' 9 | #add_group "Label", "lib/qed/directory" 10 | end 11 | end 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /try/fixtures/example.rb: -------------------------------------------------------------------------------- 1 | class Example 2 | 3 | def initialize(a=1) 4 | @a = a 5 | end 6 | 7 | def f(x,y) 8 | @a * x + y 9 | end 10 | 11 | def q(x,y) 12 | x % y 13 | end 14 | 15 | def self.m(a,b) 16 | a * b 17 | end 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /bin/lemons: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # NOTE: This command would have been called `lemon` but there turns 4 | # out to be a command by that name used by SQLite 2.x, so 5 | # we have avoded the name clash by using `lemonade` instead. 6 | 7 | require 'lemon/cli' 8 | Lemon.cli(*ARGV) 9 | -------------------------------------------------------------------------------- /work/old-tests/case_example.rb: -------------------------------------------------------------------------------- 1 | Test.case "Example Case" do 2 | 3 | test "Example Test" do 4 | "example".assert == "example" 5 | end 6 | 7 | context "Example Context" do 8 | 9 | test do 10 | "example".assert == "example" 11 | end 12 | 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /try/case_pending.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | test "one and one is two"do 8 | Example.new.f(1,1).assert == 2 9 | end 10 | 11 | test "pending" do 12 | raise NotImplementedError 13 | end 14 | 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /try/case_scope.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | test "foo is available" do 8 | foo.assert == "foo" 9 | Example.new.f(1,2) 10 | end 11 | 12 | end 13 | 14 | # Helper method. 15 | def foo 16 | "foo" 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /try/case_singleton.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example.singleton_class do 4 | 5 | method :m do 6 | 7 | setup "Example class" do 8 | Example 9 | end 10 | 11 | test "using singleton_class" do 12 | Example.m(1,1).assert == 1 13 | end 14 | 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /work/deprecated/test/fixtures/case_inclusion.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | require 'helper.rb' 4 | 5 | testcase X do 6 | 7 | include HelperMixin 8 | 9 | unit :a => "Returns a String" do 10 | X.new.a 11 | end 12 | 13 | unit :b => "Returns a String" do 14 | X.new.b 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /try/case_error.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | test "one and one is two" do 8 | ExampleUnknown.new.f(1,1).assert == 2 9 | end 10 | 11 | test "two and two is four" do 12 | ExampleUnknown.new.f(2,2).assert == 4 13 | end 14 | 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /try/case_fail.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | test "one and one is two" do 8 | Example.new.f(1,1).assert == 2 9 | end 10 | 11 | test "two and two is four" do 12 | ex = Example.new 13 | ex.f(1,2).assert == 4 14 | end 15 | 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /lib/lemon/test_world.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION < '1.9' 2 | require 'lemon/ignore_callers' 3 | else 4 | require_relative 'ignore_callers' 5 | end 6 | 7 | module Lemon 8 | 9 | # The World module is the base class for all Lemon test scopes. 10 | # To add test specific helper methods for your tests, place them here. 11 | # 12 | class World < Module 13 | end 14 | 15 | end 16 | 17 | -------------------------------------------------------------------------------- /Reapfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ignore *%w{ .yardoc doc log pkg tmp SPEC*.md web } 4 | 5 | desc "run all tests" 6 | task :test => [:unit, :qed] 7 | 8 | desc 'run lemon unit tests (via shell command)' 9 | task :unit do 10 | system 'lemons test -Ilib -Itest/fixtures test/*.rb' 11 | end 12 | 13 | desc 'run qed demonstrations' 14 | task :qed do 15 | system 'qed -Ilib spec/' 16 | end 17 | 18 | -------------------------------------------------------------------------------- /work/deprecated/test/fixtures/case_complete.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | testcase X do 4 | 5 | unit :a => "Returns a String" do 6 | X.new.a 7 | end 8 | 9 | unit :b => "Returns a String" do 10 | X.new.b 11 | end 12 | 13 | unit :c => "Returns a String" do 14 | X.new.c 15 | end 16 | 17 | end 18 | 19 | testcase Y do 20 | 21 | unit :q => "Returns a String" do 22 | Y.new.q 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /lib/lemon/core_ext/kernel.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | 3 | unless method_defined?(:qua_class) 4 | 5 | def qua_class(&block) 6 | if block_given? 7 | (class << self; self; end).class_eval(&block) 8 | else 9 | (class << self; self; end) 10 | end 11 | end 12 | 13 | alias :quaclass :qua_class 14 | end 15 | 16 | unless method_defined?(:singleton_class) 17 | alias :singleton_class :quaclass 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/lemon/ignore_callers.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # 4 | def self.ignore_callers 5 | ignore_path = File.expand_path(File.join(__FILE__, '../../..')) 6 | ignore_regexp = Regexp.new(Regexp.escape(ignore_path)) 7 | [ ignore_regexp, /bin\/lemon/ ] 8 | end 9 | 10 | # 11 | def self.setup_ignore_callers 12 | $RUBY_IGNORE_CALLERS ||= [] 13 | $RUBY_IGNORE_CALLERS.concat(ignore_callers) 14 | end 15 | 16 | end 17 | 18 | # 19 | Lemon.setup_ignore_callers 20 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | file: ~ 4 | subject: ~ 5 | mailto: 6 | - ruby-talk@ruby-lang.org 7 | - rubyworks-mailinglist@googlegroups.com 8 | #parts: [readme] 9 | 10 | gem: 11 | gemspec: pkg/lemon.gemspec 12 | active: true 13 | 14 | github: 15 | gh_pages: web 16 | 17 | dnote: 18 | title: Source Notes 19 | labels: ~ 20 | output: log/notes.html 21 | 22 | yard: 23 | yardopts: true 24 | priority: 2 25 | active: false 26 | 27 | qed: 28 | files: demo 29 | 30 | qedoc: 31 | title: Lemony Testing 32 | files: demo 33 | output: DEMOS.md 34 | 35 | vclog: 36 | output: 37 | - log/history.html 38 | - log/changes.html 39 | 40 | -------------------------------------------------------------------------------- /demo/applique/fs.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | #Before :demo do 4 | # FileUtils.rm_r('lib') 5 | # FileUtils.rm_r('test') 6 | #end 7 | 8 | When "Given an example script in '(((.*?)))' as follows" do |name, text| 9 | #name = File.join('tmp', name) if /^tmp/ !~ name 10 | dir = File.dirname(name) 11 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 12 | File.open(name, 'w'){ |w| w << text } 13 | end 14 | 15 | When "given a test case in '(((.*?)))' as follows" do |name, text| 16 | #name = File.join('tmp', name) if /^tmp/ !~ name 17 | dir = File.dirname(name) 18 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 19 | File.open(name, 'w'){ |w| w << text } 20 | end 21 | 22 | -------------------------------------------------------------------------------- /try/case_pass.rb: -------------------------------------------------------------------------------- 1 | covers 'example.rb' 2 | 3 | test_case Example do 4 | 5 | method :f do 6 | 7 | setup "without multipler" do 8 | @ex = Example.new 9 | end 10 | 11 | test "1,2" do 12 | @ex.f(1,2).assert == 3 13 | end 14 | 15 | test "2,2" do 16 | @ex.f(2,2).assert == 4 17 | end 18 | 19 | 20 | setup "with multipler" do 21 | @ex = Example.new(2) 22 | end 23 | 24 | test "1,2" do 25 | @ex.f(1,2).assert == 4 26 | end 27 | 28 | test "2,2" do 29 | @ex.f(2,2).assert == 6 30 | end 31 | 32 | teardown do 33 | # ... 34 | end 35 | 36 | #class_method :m do 37 | # Example.m(1,1).assert == 1 38 | #end 39 | 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/lemon/cli/obrother.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module CLI 4 | 5 | require 'lemon/cli/base' 6 | 7 | # The ol' "Sing it, Brother" Command. 8 | # 9 | class OBrother < Base 10 | 11 | # 12 | def command_run(argv) 13 | if argv.any?{ |a| a.downcase == 'good' } 14 | show_ascii_art 15 | else 16 | puts "No, they are GOOD!" 17 | end 18 | end 19 | 20 | # 21 | def show_ascii_art 22 | string = File.read(File.dirname(__FILE__) + '/lemon.ascii') 23 | begin 24 | require 'ansi' 25 | puts string.ansi(:yellow) 26 | rescue LoadError 27 | puts string 28 | end 29 | end 30 | 31 | end 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /work/deprecated/command/abstract.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | module Command 3 | require 'optparse' 4 | 5 | # Lemon Command-line tool base class. 6 | class Abstract 7 | 8 | # Used to map command-line options to command classes. 9 | # This must be overridden in subclasses, and return an 10 | # array of of options, e.g. [ '-g', '--generate']. 11 | def self.options 12 | raise "not implemented" 13 | end 14 | 15 | # Stores a list of command classes. 16 | def self.commands 17 | @commands ||= [] 18 | end 19 | 20 | # When this class is inherited, it is registered to the commands list. 21 | def self.inherited(command_class) 22 | commands << command_class 23 | end 24 | 25 | end 26 | 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /work/deprecated/test/case_coverage_analyzer.rb: -------------------------------------------------------------------------------- 1 | covers 'lemon/controller/coverage_analyzer' 2 | 3 | testcase Lemon::CoverageAnalyzer do 4 | 5 | setup "Coverage of public units of an incomplete test" do 6 | @memo_instance ||= ( 7 | files = ['test/fixtures/case_incomplete.rb'] 8 | Lemon::CoverageAnalyzer.new(files) 9 | ) 10 | end 11 | 12 | unit :covered => 'returns a list of covered units' do |ca| 13 | ca.covered.assert.is_a?(Array) 14 | end 15 | 16 | unit :uncovered => 'returns a list of uncovered units' do |ca| 17 | ca.uncovered.assert.is_a?(Array) 18 | end 19 | 20 | unit :current => 'returns a current Snapshot of all units in the system' do |ca| 21 | ca.current.assert.is_a?(Lemon::Snapshot) 22 | end 23 | 24 | end 25 | 26 | -------------------------------------------------------------------------------- /lib/lemon/test_class.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'lemon/test_module' 4 | 5 | # Subclass of TestModule used for classes. 6 | # It's basically the same class. 7 | # 8 | class TestClass < TestModule 9 | 10 | private 11 | 12 | # 13 | # Make sure the target is a class. 14 | # 15 | def validate_settings 16 | raise "#{@target} is not a module" unless Class === @target 17 | end 18 | 19 | # 20 | # The type of testcase. 21 | # 22 | def type 23 | 'Class' 24 | end 25 | 26 | # Evaluation scope for {TestClass}. 27 | # 28 | class DSL < TestModule::DSL 29 | 30 | # 31 | # The class for which this is a DSL context. 32 | # 33 | def context_class 34 | TestClass 35 | end 36 | 37 | end 38 | 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /Indexfile: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lemon 3 | summary: Pucker-strength Unit Testing 4 | 5 | description: 6 | Lemon is a unit testing framework that tightly correlates 7 | class to test case and method to test unit. 8 | 9 | authors: 10 | - Thomas Sawyer 11 | 12 | resources: 13 | home: http://rubyworks.github.com/lemon 14 | code: http://github.com/rubyworks/lemon 15 | bugs: http://github.com/rubyworks/lemon/issues 16 | 17 | requirements: 18 | - rubytest 19 | - ae 20 | - ansi 1.3+ 21 | - detroit (build) 22 | - reap (build) 23 | - qed (test) 24 | - name: ripper 25 | engines: 26 | - ruby 1.8~ 27 | optional: true 28 | 29 | repositories: 30 | upstream: git://github.com/rubyworks/lemon.git 31 | 32 | organizaiton: Rubyworks 33 | 34 | created: 2009-10-25 35 | 36 | copyrights: 37 | - 2009 Rubyworks (BSD-2-Clause) 38 | 39 | -------------------------------------------------------------------------------- /lib/lemon/coverage/formats/compact.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/coverage/formats/abstract' 2 | 3 | module Lemon::CoverReports 4 | 5 | class Compact < Abstract 6 | 7 | # 8 | def render 9 | 10 | unless covered_units.empty? 11 | puts "\nCovered Units: " 12 | puts covered_units.map{ |u| u.to_s.ansi(:green) }.sort.join(', ') 13 | end 14 | 15 | unless undefined_units.empty? 16 | puts "\nOvercovered Units:" 17 | puts undefined_units.map{ |u| u.to_s.ansi(:cyan) }.sort.join(', ') 18 | end 19 | 20 | unless uncovered_units.empty? 21 | puts "\nUncovered Units: " 22 | puts uncovered_units.map{ |u| u.to_s.ansi(:yellow) }.sort.join(', ') 23 | end 24 | 25 | unless uncovered_cases.empty? 26 | puts "\nUncovered Cases: " 27 | puts uncovered_cases.map{ |m| m.name.ansi(:red) }.sort.join(', ') 28 | end 29 | 30 | puts 31 | puts tally 32 | end 33 | 34 | end 35 | 36 | end 37 | 38 | -------------------------------------------------------------------------------- /lib/lemon/coverage/formats/abstract.rb: -------------------------------------------------------------------------------- 1 | module Lemon::CoverReports 2 | 3 | class Abstract 4 | 5 | require 'ansi/core' 6 | 7 | def initialize(coverage) 8 | @coverage = coverage 9 | end 10 | 11 | # 12 | attr :coverage 13 | 14 | # 15 | def render 16 | end 17 | 18 | def covered_units 19 | coverage.covered 20 | end 21 | 22 | def uncovered_units 23 | coverage.uncovered 24 | end 25 | 26 | def undefined_units 27 | coverage.undefined 28 | end 29 | 30 | def uncovered_cases 31 | coverage.uncovered_cases 32 | end 33 | 34 | # 35 | def tally 36 | c = covered_units.size 37 | u = uncovered_units.size 38 | t = c + u 39 | 40 | pc = c * 100 / t 41 | pu = u * 100 / t 42 | 43 | "#{pc}% #{c}/#{t} covered, #{pu}% #{u}/#{t} uncovered" + 44 | " (#{undefined_units.size} undefined units, #{uncovered_cases.size} uncovered cases)" 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | -------------------------------------------------------------------------------- /work/deprecated/model/dsl/subject.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module DSL 4 | 5 | # 6 | module Subject 7 | 8 | # Setup is used to set things up for each unit test. 9 | # The setup procedure is run before each unit. 10 | # 11 | # @param [String] description 12 | # A brief description of what the setup procedure sets-up. 13 | # 14 | def Setup(description=nil, &procedure) 15 | if procedure 16 | @subject = TestSubject.new(@test_case, description, &procedure) 17 | end 18 | end 19 | 20 | alias_method :setup, :Setup 21 | 22 | alias_method :Concern, :Setup 23 | alias_method :concern, :Setup 24 | 25 | alias_method :Subject, :Setup 26 | alias_method :subject, :Setup 27 | 28 | # Teardown procedure is used to clean-up after each unit test. 29 | # 30 | def Teardown(&procedure) 31 | @subject.teardown = procedure 32 | end 33 | 34 | alias_method :teardown, :Teardown 35 | 36 | end 37 | 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/lemon/coverage/formats/outline.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/coverage/formats/abstract' 2 | 3 | module Lemon::CoverReports 4 | 5 | class Outline < Abstract 6 | 7 | # 8 | def render 9 | 10 | unless covered_units.empty? 11 | puts "\nCovered Units:" 12 | covered_units.map do |unit| 13 | puts "* #{unit.to_s.ansi(:green)}" 14 | end.join(", ") 15 | end 16 | 17 | unless uncovered_units.empty? 18 | puts "\nUncovered Units:" 19 | uncovered_units.map do |unit| 20 | puts "* #{unit.to_s.ansi(:yellow)}" 21 | end 22 | end 23 | 24 | unless undefined_units.empty? 25 | puts "\nUndefined Units:" 26 | unc = undefined_units.map do |unit| 27 | puts "* #{unit.to_s.ansi(:red)}" 28 | end 29 | end 30 | 31 | unless uncovered_cases.empty? 32 | puts "\nUncovered Cases:" 33 | uncovered_cases.map do |mod| 34 | puts "* #{mod.name.ansi(:cyan)}" 35 | end 36 | end 37 | 38 | puts 39 | puts tally 40 | end 41 | 42 | end 43 | 44 | end 45 | 46 | -------------------------------------------------------------------------------- /work/old-tests/feature_example.rb: -------------------------------------------------------------------------------- 1 | Test::Feature "Addition" do 2 | To "avoid silly mistakes" 3 | As "a math idiot" 4 | We "need to calculate the sum of numbers" 5 | 6 | Scenario "Add two numbers" do 7 | Given "I have a calculator" 8 | Given "I have entered 50 into the calculator" 9 | Given "I have entered 70 into the calculator" 10 | When "I press add" 11 | Then "the result should be 120 on the screen" 12 | end 13 | 14 | Scenario "Add three numbers" do 15 | Given "I have a calculator" 16 | Given "I have entered 50 into the calculator" 17 | Given "I have entered 70 into the calculator" 18 | Given "I have entered 90 into the calculator" 19 | When "I press add" 20 | Then "the result should be 210 on the screen" 21 | end 22 | 23 | Given 'I have a calculator' do 24 | require 'calculator' 25 | @calculator = Calculator.new 26 | end 27 | 28 | Given 'I have entered (((\d+))) into the calculator' do |n| 29 | @calculator.push n.to_i 30 | end 31 | 32 | When 'I press add' do 33 | @result = @calculator.add 34 | end 35 | 36 | Then 'the result should be (((\d+))) on the screen' do |n| 37 | @result.assert == n.to_i 38 | end 39 | end 40 | 41 | -------------------------------------------------------------------------------- /lib/lemon/test_advice.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | #-- 4 | # TODO: Probably can replace advice with simple hash. 5 | #++ 6 | 7 | # Test Advice 8 | class TestAdvice 9 | 10 | # The test case to which this advice belongs. 11 | #attr :context 12 | 13 | # 14 | attr :table 15 | 16 | # New case instance. 17 | def initialize 18 | @table = Hash.new{ |h,k| h[k] = {} } 19 | end 20 | 21 | # 22 | def initialize_copy(original) 23 | @table = original.table.clone 24 | end 25 | 26 | # 27 | def [](type) 28 | @table[type.to_sym] 29 | end 30 | 31 | =begin 32 | # 33 | #def teardown=(procedure) 34 | # @teardown = procedure 35 | #end 36 | 37 | # Setup. 38 | def setup(scope=nil) 39 | if scope 40 | scope.instance_eval(&@setup) 41 | else 42 | @setup 43 | end 44 | end 45 | 46 | # Teardown. 47 | def teardown(scope=nil) 48 | if scope 49 | scope.instance_eval(&@teardown) if @teardown 50 | else 51 | @teardown 52 | end 53 | end 54 | 55 | # Returns the description with newlines removed. 56 | def to_s 57 | description.gsub(/\n/, ' ') 58 | end 59 | =end 60 | 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /work/deprecated/model/test.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/model/test_suite' 2 | 3 | module Test 4 | extend self 5 | 6 | # 7 | def covers(script) 8 | Lemon.suite.dsl.covers(script) 9 | end 10 | alias :Covers :covers 11 | 12 | alias :coverage :covers 13 | alias :Coverage :covers 14 | 15 | # Define a general test case. 16 | def case(target, &block) 17 | Lemon.suite.dsl.test_case(target, &block) 18 | end 19 | alias :Case :case 20 | 21 | # Define a class test. 22 | def class(target_class, &block) 23 | Lemon.suite.dsl.test_class(target_class, &block) 24 | end 25 | alias :Class :class 26 | 27 | # Define a module test. 28 | def module(target_module, &block) 29 | Lemon.suite.dsl.test_module(target_module, &block) 30 | end 31 | alias :Module :module 32 | 33 | # Define a test feature. 34 | def feature(target, &block) 35 | Lemon.suite.dsl.test_feature(target, &block) 36 | end 37 | alias :Feature :feature 38 | 39 | # 40 | #def Before(match=nil, &block) 41 | # Lemon.suite.Before(match, &block) 42 | #end 43 | 44 | # 45 | #def After(match=nil, &block) 46 | # Lemon.suite.After(match, &block) 47 | #end 48 | 49 | # 50 | #def Helper(script) 51 | # Lemon.suite.Helper(script) 52 | #end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /lib/lemon/cli.rb: -------------------------------------------------------------------------------- 1 | #require 'lemon/cli/runner' 2 | require 'lemon/cli/generate' 3 | require 'lemon/cli/scaffold' 4 | require 'lemon/cli/coverage' 5 | require 'lemon/cli/obrother' 6 | 7 | module Lemon 8 | 9 | # CLI Interfaces handle all lemon sub-commands. 10 | # 11 | module CLI 12 | end 13 | 14 | #-- 15 | # TODO: Use Lemon::CLI::Runner and have it delegate to RubyTest ? 16 | #++ 17 | 18 | # Command line interface takes the first argument off `argv` to determine 19 | # the subcommand: `test`, `cov` or `gen`. If `test`, then Lemon delegates 20 | # control to Ruby Test. 21 | # 22 | def self.cli(*argv) 23 | cmd = argv.shift 24 | case cmd 25 | when 't', 'test', 'run' 26 | require 'lemon' 27 | require 'rubytest' 28 | Test::Runner.cli(*ARGV) 29 | #Lemon::CLI::Test.new.run(argv) 30 | when 'g', 'gen', 'generate', 'generator' 31 | Lemon::CLI::Generate.run(argv) 32 | when 's', 'sca', 'scaffold' 33 | Lemon::CLI::Scaffold.run(argv) 34 | when 'c', 'cov', 'cover', 'coverage' 35 | Lemon::CLI::Coverage.run(argv) 36 | when 'are' 37 | Lemon::CLI::OBrother.run(argv) 38 | else 39 | # run tests instead? 40 | puts "invalid lemon command -- #{cmd}" 41 | exit -1 42 | end 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /lib/lemon/coverage/formats/verbose.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/coverage/formats/abstract' 2 | 3 | module Lemon::CoverReports 4 | 5 | class Verbose < Abstract 6 | 7 | # 8 | def render 9 | 10 | unless covered_units.empty? 11 | puts "\nCovered Cases: " 12 | covered_units.map do |unit| 13 | puts unit_line(unit, :green) 14 | end 15 | end 16 | 17 | unless uncovered_units.empty? 18 | puts "\nUncovered Units: " 19 | uncovered_units.map do |unit| 20 | puts unit_line(unit, :yellow) 21 | end 22 | end 23 | 24 | unless undefined_units.empty? 25 | puts "\nUndefined Units: " 26 | undefined_units.map do |unit| 27 | puts unit_line(unit, :red) 28 | end 29 | end 30 | 31 | unless uncovered_cases.empty? 32 | puts "\nUncovered Cases: " 33 | uncovered_cases.map do |mod| 34 | puts "* " + mod.name.to_s.ansi(:yellow) 35 | end 36 | end 37 | 38 | puts 39 | puts tally 40 | end 41 | 42 | # 43 | def unit_line(unit, color) 44 | data = [unit.to_s.ansi(color), unit.access.to_s, unit.singleton? ? 'class method' : 'instance method'] 45 | "* %s %s %s" % data 46 | end 47 | 48 | end 49 | 50 | end 51 | 52 | -------------------------------------------------------------------------------- /work/reference/dynamic_constant_lookup.rb: -------------------------------------------------------------------------------- 1 | # 2 | module M 3 | def m1; "m1"; end 4 | module N 5 | def self.n1; "n1"; end 6 | end 7 | end 8 | 9 | class X 10 | class << self 11 | alias _new new 12 | def new(&block) 13 | klass = Class.new(self) 14 | klass.module_eval(&block) 15 | klass._new 16 | end 17 | end 18 | 19 | def m ; m1 ; end 20 | def n ; N.n1 ; end 21 | end 22 | 23 | x = X.new do 24 | include M 25 | end 26 | 27 | p x.m 28 | p x.n 29 | 30 | 31 | exit 32 | 33 | class X 34 | attr :set 35 | def initialize(&block) 36 | @set = {} 37 | instance_eval(&block) 38 | end 39 | def defset(name, &block) 40 | @set[name] = block 41 | end 42 | def include(*mods) 43 | (class << self; self; end).class_eval do 44 | include *mods 45 | end 46 | end 47 | end 48 | 49 | # 50 | module M 51 | def m1; "m1"; end 52 | module N 53 | def self.n1; "n1"; end 54 | end 55 | end 56 | 57 | x = X.new do 58 | 59 | include M 60 | 61 | #Before /returns/ do |unit| 62 | # puts "returns something" 63 | #end 64 | 65 | defset :m do 66 | m1 == "m1" 67 | end 68 | 69 | defset :n do 70 | N.n1 == "n1" 71 | end 72 | 73 | end 74 | 75 | x.set[:m].call 76 | x.set[:n].call 77 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | - Indexfile 4 | - VERSION 5 | authors: 6 | - name: Thomas Sawyer 7 | email: transfire@gmail.com 8 | copyrights: 9 | - holder: Rubyworks 10 | year: '2009' 11 | license: BSD-2-Clause 12 | requirements: 13 | - name: rubytest 14 | - name: ae 15 | - name: ansi 16 | version: 1.3+ 17 | - name: detroit 18 | groups: 19 | - build 20 | development: true 21 | - name: reap 22 | groups: 23 | - build 24 | development: true 25 | - name: qed 26 | groups: 27 | - test 28 | development: true 29 | - name: ripper 30 | optional: true 31 | engines: 32 | - name: ruby 33 | version: 1.8~ 34 | dependencies: [] 35 | alternatives: [] 36 | conflicts: [] 37 | repositories: 38 | - uri: git://github.com/proutils/lemon.git 39 | scm: git 40 | name: upstream 41 | resources: 42 | home: http://rubyworks.github.com/lemon 43 | code: http://github.com/rubyworks/lemon 44 | bugs: http://github.com/rubyworks/lemon/issues 45 | extra: {} 46 | load_path: 47 | - lib 48 | revision: 0 49 | created: '2009-10-25' 50 | summary: Pucker-strength Unit Testing 51 | title: Lemon 52 | name: lemon 53 | description: ! 'Lemon is a unit testing framework that tightly correlates 54 | 55 | class to test case and method to test unit.' 56 | organization: rubyworks 57 | version: 0.9.1 58 | date: '2012-03-09' 59 | -------------------------------------------------------------------------------- /lib/lemon/cli/coverage.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module CLI 4 | 5 | require 'lemon/cli/base' 6 | 7 | # Coverage Command 8 | class Coverage < Base 9 | 10 | # Ouput coverage report. 11 | def command_run(test_files) 12 | require 'lemon/coverage/analyzer' 13 | 14 | #loadpath = options[:loadpath] || [] 15 | #requires = options[:requires] || [] 16 | 17 | #loadpath.each{ |path| $LOAD_PATH.unshift(path) } 18 | #requires.each{ |path| require(path) } 19 | 20 | $stderr.print "Calculating... " 21 | $stderr.flush 22 | 23 | cover = Lemon::CoverageAnalyzer.new(test_files, options) 24 | 25 | cover.calculate # this just helps calcs get done up front 26 | 27 | $stderr.puts 28 | 29 | cover.render 30 | end 31 | 32 | # 33 | def command_parse(argv) 34 | option_parser.banner = "Usage: lemonade coverage [options] [files ...]" 35 | #option_parser.separator("Check test coverage.") 36 | 37 | option_namespaces 38 | option_private 39 | option_zealous 40 | option_output 41 | option_format 42 | option_loadpath 43 | option_requires 44 | 45 | option_parser.parse!(argv) 46 | end 47 | 48 | end 49 | 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /lib/lemon/test_setup.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Test Subject - Setup and Teardown code. 4 | class TestSetup 5 | 6 | # The test case to which this advice belong. 7 | attr :context 8 | 9 | # The description of this concern. Make this 10 | # as detailed as you wish. 11 | attr :description 12 | 13 | # Setup procedure. 14 | attr :setup 15 | 16 | # Teardown procedure. 17 | attr :teardown 18 | 19 | # New case instance. 20 | def initialize(context, description, options={}, &setup) 21 | @context = context 22 | @description = description.to_s 23 | #@singleton = options[:singleton] 24 | #@type = options[:type] || :context 25 | @setup = [setup].flatten 26 | @teardown = [] 27 | end 28 | 29 | # 30 | def teardown=(procedure) 31 | @teardown = [procedure] 32 | end 33 | 34 | # Setup. 35 | def run_setup(scope) 36 | setup.each do |proc| 37 | scope.instance_eval(&proc) 38 | end 39 | end 40 | 41 | # Teardown. 42 | def run_teardown(scope) 43 | teardown.each do |proc| 44 | scope.instance_eval(&proc) 45 | end 46 | end 47 | 48 | # Returns the description with newlines removed. 49 | def to_s 50 | description.gsub(/\n/, ' ') 51 | end 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /lib/lemon/cli/test.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module CLI 4 | 5 | require 'lemon/cli/base' 6 | 7 | # Test Command 8 | class Test < Base 9 | 10 | # Run unit tests. 11 | def command_run(scripts) 12 | require 'lemon/runner' 13 | 14 | loadpath = options[:loadpath] || ['lib'] # + ['lib'] ? 15 | requires = options[:requires] || [] 16 | 17 | loadpath.each{ |path| $LOAD_PATH.unshift(path) } 18 | requires.each{ |path| require(path) } 19 | 20 | #suite = Lemon::Test::Suite.new(files, :cover=>cover) 21 | #runner = Lemon::Runner.new(suite, :format=>format, :cover=>cover, :namespaces=>namespaces) 22 | 23 | runner = Lemon::TestRunner.new( 24 | scripts, :format=>options[:format], :namespaces=>options[:namespaces] 25 | ) 26 | 27 | success = runner.run 28 | 29 | exit -1 unless success 30 | end 31 | 32 | # 33 | def command_parse(argv) 34 | option_parser.banner = "Usage: lemonade test [options] [files ...]" 35 | #option_parser.separator("Run unit tests.") 36 | 37 | option_format 38 | option_verbose 39 | option_namespaces 40 | option_loadpath 41 | option_requires 42 | 43 | option_parser.parse!(argv) 44 | end 45 | 46 | end 47 | 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /work/deprecated/test/case_test_case_dsl.rb: -------------------------------------------------------------------------------- 1 | testcase Lemon::TestCase::DSL do 2 | 3 | before do 4 | @files = ['test/fixtures/case_inclusion.rb'] 5 | end 6 | 7 | setup "Modules included in a test case are accessible by the unit tests" do 8 | ts = Lemon::TestSuite.new(@files) 9 | tc = ts.test_cases.first # the only one 10 | tc.dsl 11 | end 12 | 13 | unit :include, "allows access to module methods" do |dsl| 14 | mod = Module.new{ def x; "x"; end } 15 | dsl.include(mod) 16 | # how to test? 17 | end 18 | 19 | # I do not think it is possible to make this work using dyanmic module 20 | # construction. Please correct me if you know otherwise. To fix 21 | # would mean turning test cases into classes instead of objects. Maybe 22 | # we will do this in the future. 23 | omit unit :include, "allows access to nested modules" do |dsl| 24 | mod = Module.new{ N = 1 } 25 | dsl.include(mod) 26 | dsl::N == 1 27 | end 28 | 29 | setup "Test cases are augmented by before and afters procedures" do 30 | ts = Lemon::TestSuite.new(@files) 31 | tc = ts.test_cases.first # the only one 32 | tc.dsl 33 | end 34 | 35 | unit :before => "setup a pre-testcase procedure" do |dsl| 36 | dsl.before{ } 37 | # how to test? 38 | end 39 | 40 | unit :after => "setup a post-testcase procedure" do |dsl| 41 | dsl.after{ } 42 | # how to test? 43 | end 44 | 45 | end 46 | 47 | -------------------------------------------------------------------------------- /lib/lemon/test_class_method.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'lemon/test_method' 4 | 5 | # Subclass of TestMethod used for class methods. 6 | # It's basically the same class. 7 | # 8 | class TestClassMethod < TestMethod 9 | 10 | # Description of the type of test case. 11 | def type 12 | 'Class Method' 13 | end 14 | 15 | # If class method, returns target method's name prefixed with double colons. 16 | # If instance method, then returns target method's name prefixed with hash 17 | # character. 18 | def name 19 | "::#{target}" 20 | end 21 | 22 | # Returns the prefixed method name. 23 | def to_s 24 | "::#{target}" 25 | end 26 | 27 | # Returns the fully qulaified name of the target method. This is 28 | # the standard interface used by RubyTest. 29 | def unit 30 | "#{context}.#{target}" 31 | end 32 | 33 | # For a class method, the target class is the meta-class. 34 | def target_class 35 | @target_class ||= (class << context.target; self; end) 36 | end 37 | 38 | # 39 | def class_method? 40 | true 41 | end 42 | 43 | # Scope for evaluating class method test definitions. 44 | # 45 | class DSL < TestMethod::DSL 46 | 47 | # 48 | # The class for which this is a DSL context. 49 | # 50 | def context_class 51 | TestClassMethod 52 | end 53 | 54 | end 55 | 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Lemon - Pucker-Strength Unit Testing 2 | 3 | Copyright (c) 2009 Rubyworks. All rights reserved 4 | 5 | Licensed (spdx) BSD-2-Clause 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are 8 | permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this list of 11 | conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 14 | of conditions and the following disclaimer in the documentation and/or other materials 15 | provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 18 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | (http://rubyworks.github.com/lemon) 28 | -------------------------------------------------------------------------------- /demo/coverage/03_extensions.md: -------------------------------------------------------------------------------- 1 | ## Core Extension Coverage 2 | 3 | ### Kernel Extensions 4 | 5 | Given an example script in 'lib/extensions_example.rb' as follows: 6 | 7 | module Kernel 8 | def f1; "f1"; end 9 | def f2; "f2"; end 10 | def f3; "f3"; end 11 | end 12 | 13 | And given a test case in 'test/extensions_example_case.rb' as follows: 14 | 15 | Covers 'extensions_example.rb' 16 | 17 | TestCase Kernel do 18 | method :f1 do 19 | test do 20 | fl.assert == "f1" 21 | end 22 | end 23 | method :f2 do 24 | test do 25 | f2.assert == "f2" 26 | end 27 | end 28 | end 29 | 30 | And we get the coverage information via CoverageAnalyer. 31 | 32 | require 'lemon' 33 | 34 | tests = ['test/extensions_example_case.rb'] 35 | 36 | coverage = Lemon::CoverageAnalyzer.new(tests, :loadpath=>'lib') 37 | 38 | Then we should see that there are two covered units, #f1 and #f2. 39 | 40 | coverage.covered_units.size.assert == 2 41 | 42 | units = coverage.covered_units.map{ |u| u.to_s } 43 | 44 | units.assert.include?('Kernel#f1') 45 | units.assert.include?('Kernel#f2') 46 | 47 | units.refute.include?('Kernel#f3') 48 | 49 | And we should see one unconvered unit, #f3. 50 | 51 | coverage.uncovered_units.size.assert == 1 52 | 53 | units = coverage.uncovered_units.map{ |u| u.to_s } 54 | 55 | units.assert.include?('Kernel#f3') 56 | 57 | There should be zero uncovered cases. 58 | 59 | coverage.uncovered_cases == [] 60 | 61 | And zero undefined unit. 62 | 63 | coverage.undefined_units == [] 64 | 65 | 66 | -------------------------------------------------------------------------------- /lib/lemon.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Access to metadata. 4 | def self.metadata 5 | @metadata ||= ( 6 | require 'yaml' 7 | YAML.load(File.new(File.dirname(__FILE__) + '/lemon.yml')) 8 | ) 9 | end 10 | 11 | # Access to project metadata as constants. 12 | def self.const_missing(name) 13 | key = name.to_s.downcase 14 | metadata[key] || super(name) 15 | end 16 | 17 | end 18 | 19 | # Ruby Test standard location for test objects. 20 | $TEST_SUITE ||= [] 21 | 22 | require 'lemon/test_class' 23 | 24 | module Lemon 25 | 26 | # Lemon's toplevel test domain specific language. 27 | module DSL 28 | 29 | # Require script and record it. 30 | # 31 | # @param [STRING] script 32 | # The load path of a script. 33 | # 34 | def covers(script) 35 | # TODO: record coverage list 36 | require script 37 | end 38 | alias :Covers :covers 39 | 40 | # Define a class/module test case. 41 | # 42 | # @param [Module,Class] target 43 | # The class or module the tests will target. 44 | # 45 | # @yield 46 | # Scope in which to define unit/method testcases. 47 | # 48 | def test_case(target, &block) 49 | case target 50 | when Class 51 | $TEST_SUITE << Lemon::TestClass.new(:target=>target, &block) 52 | when Module 53 | $TEST_SUITE << Lemon::TestModule.new(:target=>target, &block) 54 | else 55 | if defined?(super) 56 | super(target, &block) 57 | else 58 | raise 59 | end 60 | end 61 | end 62 | 63 | alias :TestCase :test_case 64 | alias :testcase :test_case 65 | 66 | end 67 | 68 | end 69 | 70 | extend Lemon::DSL 71 | -------------------------------------------------------------------------------- /MANIFEST.txt: -------------------------------------------------------------------------------- 1 | #!mast .yardopts .ruby bin demo lib man qed spec test try [A-Z]*.* 2 | .yardopts 3 | .ruby 4 | bin/lemons 5 | lib/lemon/ae.rb 6 | lib/lemon/cli/base.rb 7 | lib/lemon/cli/coverage.rb 8 | lib/lemon/cli/generate.rb 9 | lib/lemon/cli/lemon.ascii 10 | lib/lemon/cli/obrother.rb 11 | lib/lemon/cli/scaffold.rb 12 | lib/lemon/cli/test.rb 13 | lib/lemon/cli.rb 14 | lib/lemon/core_ext/kernel.rb 15 | lib/lemon/core_ext/module.rb 16 | lib/lemon/core_ext.rb 17 | lib/lemon/coverage/analyzer.rb 18 | lib/lemon/coverage/cover_unit.rb 19 | lib/lemon/coverage/formats/abstract.rb 20 | lib/lemon/coverage/formats/compact.rb 21 | lib/lemon/coverage/formats/outline.rb 22 | lib/lemon/coverage/formats/verbose.rb 23 | lib/lemon/coverage/formats/yaml.rb 24 | lib/lemon/coverage/generator.rb 25 | lib/lemon/coverage/snapshot.rb 26 | lib/lemon/coverage/source_parser.rb 27 | lib/lemon/ignore_callers.rb 28 | lib/lemon/test_advice.rb 29 | lib/lemon/test_case.rb 30 | lib/lemon/test_class.rb 31 | lib/lemon/test_class_method.rb 32 | lib/lemon/test_method.rb 33 | lib/lemon/test_module.rb 34 | lib/lemon/test_proc.rb 35 | lib/lemon/test_scope.rb 36 | lib/lemon/test_setup.rb 37 | lib/lemon/test_world.rb 38 | lib/lemon.rb 39 | lib/lemon.yml 40 | spec/applique/ae.rb 41 | spec/applique/fs.rb 42 | spec/coverage/01_complete.md 43 | spec/coverage/02_incomplete.md 44 | spec/coverage/03_extensions.md 45 | spec/coverage/applique/lemon.rb 46 | try/.test 47 | try/case_error.rb 48 | try/case_fail.rb 49 | try/case_pass.rb 50 | try/case_pending.rb 51 | try/case_scope.rb 52 | try/case_singleton.rb 53 | try/case_untested.rb 54 | try/fixtures/calculator.rb 55 | try/fixtures/example-use.rb 56 | try/fixtures/example.rb 57 | try/helpers/loadpath.rb 58 | LICENSE.txt 59 | HISTORY.md 60 | README.md 61 | SPECSHEET.md 62 | Config.rb 63 | -------------------------------------------------------------------------------- /work/deprecated/model/test_context.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | module Lemon 3 | 4 | # 5 | class TestContext 6 | 7 | # The test case to which this concern belongs. 8 | attr :test_case 9 | 10 | # The description of this concern. Make this 11 | # as detailed as you wish. 12 | attr :description 13 | 14 | # New case instance. 15 | def initialize(test_case, description, options={}, &block) 16 | @test_case = test_case 17 | @description = description.to_s 18 | @function = options[:function] || options[:singleton] 19 | @type = options[:type] || :context 20 | @block = block 21 | end 22 | 23 | # 24 | def teardown=(procedure) 25 | @teardown = procedure 26 | end 27 | 28 | # Teardown instance. 29 | def teardown(scope=nil) 30 | if scope 31 | scope.instance_eval(&@teardown) if @teardown 32 | else 33 | @teardown 34 | end 35 | end 36 | 37 | # Create instance. 38 | def setup(scope) 39 | if @block 40 | scope.instance_eval(&@block) 41 | end 42 | end 43 | 44 | def function? ; false ; end 45 | alias_method :meta?, :function? 46 | 47 | # Returns the description with newlines removed. 48 | def to_s 49 | description.gsub(/\n/, ' ') 50 | end 51 | end 52 | 53 | end 54 | =end 55 | 56 | =begin 57 | # 58 | class TestInstance < TestContext 59 | 60 | # Create instance. 61 | def setup(scope) 62 | if @block 63 | ins = scope.instance_eval(&@block) 64 | raise "target type mismatch" unless test_case.target === ins 65 | end 66 | ins 67 | end 68 | 69 | end 70 | 71 | # 72 | class TestSingleton < TestContext 73 | 74 | # Create instance. 75 | def setup(scope) 76 | if @block 77 | ins = scope.instance_eval(&@block) 78 | raise "target type mismatch" unless test_case.target == ins 79 | else 80 | ins = @test_case.target 81 | end 82 | ins 83 | end 84 | 85 | def function? ; true ; end 86 | alias_method :meta?, :function? 87 | 88 | end 89 | =end 90 | 91 | -------------------------------------------------------------------------------- /work/deprecated/features/coverage.feature: -------------------------------------------------------------------------------- 1 | Feature: Coverage 2 | As a developer 3 | In order to improve test coverge 4 | I want to able to write tests with coverage in mind 5 | And receive effective coverage reports 6 | 7 | Scenario: Complete Example Case 8 | Given a directory named "example" 9 | Given a file named "example/lib/example.rb" with: 10 | """ 11 | class X 12 | def a; "a"; end 13 | def b; "b"; end 14 | def c; "c"; end 15 | end 16 | class Y 17 | def q; "q"; end 18 | end 19 | """ 20 | Given a file named "example/test/case_complete.rb" with: 21 | """ 22 | Covers 'example' 23 | TestCase X do 24 | Unit :a => "Returns a String" do ; end 25 | Unit :b => "Returns a String" do ; end 26 | Unit :c => "Returns a String" do ; end 27 | end 28 | TestCase Y do 29 | Unit :q => "Returns a String" do ; end 30 | end 31 | """ 32 | When I cd to "example" 33 | And I run "lemon -c -Ilib test/case_complete.rb" 34 | Then the stdout should contain "0 uncovered cases" 35 | And the stdout should contain "0/4 uncovered" 36 | And the stdout should contain "0 undefined units" 37 | 38 | Scenario: Incomplete Example Case 39 | Given a directory named "example" 40 | Given a file named "example/lib/example.rb" with: 41 | """ 42 | class X 43 | def a; "a"; end 44 | def b; "b"; end 45 | def c; "c"; end 46 | end 47 | class Y 48 | def q; "q"; end 49 | end 50 | """ 51 | Given a file named "example/test/case_incomplete.rb" with: 52 | """ 53 | Covers 'example' 54 | TestCase X do 55 | Unit :a => "Returns a String" do ; end 56 | Unit :b => "Returns a String" do ; end 57 | Unit :d => "Returns a String" do ; end 58 | end 59 | """ 60 | When I cd to "example" 61 | And I run "lemon -c -Ilib test/case_incomplete.rb" 62 | Then the stdout should contain "1 uncovered cases" 63 | And the stdout should contain "1/4 uncovered" 64 | And the stdout should contain "1 undefined units" 65 | 66 | -------------------------------------------------------------------------------- /work/deprecated/model/test_base_dsl.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Base class for TestCase DSLs. 4 | class BaseDSL < Module 5 | 6 | # 7 | def initialize(context, &code) 8 | @context = context 9 | @subject = context.subject 10 | 11 | module_eval(&code) 12 | end 13 | 14 | # 15 | def context(description, &block) 16 | @context.tests << TestCase.new(@context, description, &block) 17 | end 18 | 19 | # 20 | def test(description, &procedure) 21 | test = TestProc.new( 22 | @context, 23 | :description => description, 24 | :subject => subject, 25 | &procedure 26 | ) 27 | @context.tests << test 28 | test 29 | end 30 | 31 | # Omit a test or test case from being run. 32 | # 33 | # omit test "something or other" do 34 | # # ... 35 | # end 36 | # 37 | def omit(test_or_case) 38 | test_or_case.omit = true 39 | end 40 | alias_method :Omit, :omit 41 | 42 | 43 | 44 | 45 | # Load a helper script applicable to this test case. Unlike requiring 46 | # a helper script, the #helper method will eval the file's contents 47 | # directly into the test context (using instance_eval). 48 | # 49 | # @param [String] file 50 | # File to eval into test context. 51 | # 52 | # FIXME: This is at odds with loading helpers automatically. How 53 | # to handle? 54 | def helper(file) 55 | instance_eval(File.read(file), file) 56 | end 57 | 58 | alias_method :Helper, :helper 59 | 60 | #def include(*mods) 61 | # extend *mods 62 | #end 63 | 64 | #def pending(message=nil) 65 | # raise Pending.new(message) 66 | #end 67 | 68 | 69 | # Before All and After All advice are bad form. 70 | # 71 | # # Define a "before all" procedure. 72 | # def prepare(&procedure) 73 | # before(&procedure) 74 | # end 75 | # 76 | # alias_method :Prepare, :prepare 77 | # 78 | # # Define an "after all" procedure. 79 | # def cleanup(&procedure) 80 | # after(&procedure) 81 | # end 82 | # 83 | # alias_method :Cleanup, :cleanup 84 | # 85 | 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/lemon/cli/generate.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module CLI 4 | 5 | require 'lemon/cli/base' 6 | 7 | # Generate Command 8 | # 9 | class Generate < Base 10 | 11 | # Generate test templates. 12 | def command_run(files) 13 | if i = files.index('-') 14 | options[:files] = files[0...i] 15 | options[:tests] = files[i+1..-1] 16 | else 17 | options[:files] = files 18 | options[:tests] = [] 19 | end 20 | 21 | require 'lemon/coverage/generator' 22 | 23 | loadpath = options[:loadpath] || [] 24 | requires = options[:requires] || [] 25 | 26 | loadpath.each{ |path| $LOAD_PATH.unshift(path) } 27 | requires.each{ |path| require(path) } 28 | 29 | generator = Lemon::Generator.new(options) 30 | 31 | render_map = generator.generate 32 | 33 | generate_output(render_map) 34 | end 35 | 36 | # 37 | def generate_output(render_map) 38 | render_map.each do |group, test| 39 | puts "# --- #{group} ---" 40 | puts 41 | puts test 42 | puts 43 | end 44 | end 45 | 46 | # 47 | def command_parse(argv) 48 | option_parser.banner = "Usage: lemons generate [options] [files ...]" 49 | 50 | setup_options 51 | 52 | option_parser.parse!(argv) 53 | end 54 | 55 | # 56 | def setup_options 57 | option_group 58 | option_namespaces 59 | option_private 60 | option_caps 61 | option_loadpath 62 | option_requires 63 | end 64 | 65 | # -f --file 66 | def option_group 67 | option_parser.on('-f', '--file', 'group tests by file') do 68 | options[:group] = :file 69 | end 70 | #option_parser.on('-c', '--case', 'group tests by case') do 71 | # options[:group] = :case 72 | #end 73 | end 74 | 75 | # -C --caps 76 | def option_caps 77 | option_parser.on('-C', '--caps', 'use capitalized test terms') do 78 | options[:caps] = true 79 | end 80 | end 81 | 82 | end 83 | 84 | end 85 | 86 | end 87 | -------------------------------------------------------------------------------- /lib/lemon/coverage/cover_unit.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Unit of coverage, ecapsulates a method, it's characteristics and a flag 4 | # as to whether it has been covered or not. 5 | class CoverUnit 6 | 7 | attr :target 8 | 9 | attr :method 10 | 11 | attr :singleton 12 | 13 | # 14 | def initialize(target, method, props={}) 15 | @target = target 16 | @method = method.to_sym 17 | @covered = props[:covered] 18 | @singleton = props[:singleton] ? true : false 19 | 20 | if @singleton 21 | @private = !@target.public_methods.find{ |m| m.to_sym == @method } 22 | else 23 | @private = !@target.public_instance_methods.find{ |m| m.to_sym == @method } 24 | end 25 | end 26 | 27 | # 28 | # Method access is private or protected? 29 | # 30 | def private? 31 | @private 32 | end 33 | 34 | # 35 | # Marked as covered? 36 | # 37 | def covered? 38 | @covered 39 | end 40 | 41 | # 42 | # 43 | # 44 | def singleton? 45 | @singleton 46 | end 47 | 48 | alias class_method? singleton? 49 | 50 | # 51 | # 52 | # 53 | def hash 54 | @target.hash ^ @method.hash ^ singleton.hash 55 | end 56 | 57 | # 58 | # 59 | # 60 | def to_s 61 | if singleton? 62 | "#{@target}.#{@method}" 63 | else 64 | "#{@target}##{@method}" 65 | end 66 | end 67 | alias to_str to_s 68 | 69 | # 70 | # 71 | # 72 | def eql?(other) 73 | return false unless Unit === other 74 | return false unless target == other.target 75 | return false unless method == other.method 76 | return false unless singleton == other.singleton 77 | return true 78 | end 79 | 80 | # 81 | # 82 | # 83 | def inspect 84 | "#{target}#{singleton? ? '.' : '#'}#{method}" 85 | end 86 | 87 | # 88 | # 89 | # 90 | def <=>(other) 91 | c = (target.name <=> other.target.name) 92 | return c unless c == 0 93 | return -1 if singleton && !other.singleton 94 | return 1 if !singleton && other.singleton 95 | method.to_s <=> other.method.to_s 96 | end 97 | end 98 | 99 | end 100 | -------------------------------------------------------------------------------- /work/deprecated/features/test.feature: -------------------------------------------------------------------------------- 1 | Feature: Coverage 2 | As a developer 3 | In order to improve test coverge 4 | I want to able to write unit tests that target methods 5 | And run those tests 6 | 7 | Scenario: Complete Example Case 8 | Given a directory named "example" 9 | Given a file named "example/lib/example.rb" with: 10 | """ 11 | class X 12 | def a; "a"; end 13 | def b; "b"; end 14 | def c; "c"; end 15 | end 16 | class Y 17 | def q; "q"; end 18 | end 19 | """ 20 | Given a file named "example/test/case_complete.rb" with: 21 | """ 22 | Covers 'example' 23 | TestCase X do 24 | Unit :a => "Returns a String" do ; X.new.a.assert.is_a?(String) ; end 25 | Unit :b => "Returns a String" do ; X.new.b.assert.is_a?(String) ; end 26 | Unit :c => "Returns a String" do ; X.new.c.assert.is_a?(String) ; end 27 | end 28 | TestCase Y do 29 | Unit :q => "Returns a String" do ; Y.new.q.assert.is_a?(String) ; end 30 | end 31 | """ 32 | When I cd to "example" 33 | And I run "lemon -Ilib -v test/case_complete.rb" 34 | Then the stdout should contain "4 tests" 35 | And the stdout should contain "4 pass" 36 | And the stdout should contain "0 fail" 37 | And the stdout should contain "0 err" 38 | 39 | Scenario: Incomplete Example Case 40 | Given a directory named "example" 41 | Given a file named "example/lib/example.rb" with: 42 | """ 43 | class X 44 | def a; "a"; end 45 | def b; "b"; end 46 | def c; "c"; end 47 | end 48 | class Y 49 | def q; "q"; end 50 | end 51 | """ 52 | Given a file named "example/test/case_complete.rb" with: 53 | """ 54 | Covers 'example' 55 | TestCase X do 56 | Unit :a => "Returns a String" do ; X.new.a.assert.is_a?(String) ; end 57 | Unit :b => "Returns a String" do ; X.new.b.assert.is_a?(Fixnum) ; end 58 | Unit :d => "Returns a String" do ; X.new.d.assert.is_a?(String) ; end 59 | end 60 | """ 61 | When I cd to "example" 62 | And I run "lemon -Ilib -v test/case_complete.rb" 63 | Then the stdout should contain "3 tests" 64 | And the stdout should contain "1 pass" 65 | And the stdout should contain "1 fail" 66 | And the stdout should contain "1 err" 67 | 68 | -------------------------------------------------------------------------------- /work/deprecated/model/main.rb: -------------------------------------------------------------------------------- 1 | # NOTE: This code is not being used. It is here for the time being 2 | # on the outseide change that I decide to go back to a toplevel design. 3 | 4 | require 'lemon/model/test_suite' 5 | 6 | class << self 7 | 8 | # 9 | def Covers(script) 10 | Lemon.suite.dsl.covers(script) 11 | end 12 | alias :Coverage :Covers 13 | 14 | # Define a general test case. 15 | def Case(target, &block) 16 | Lemon.suite.dsl.test_case(target, &block) 17 | end 18 | 19 | # Define a class test. 20 | def Class(target_class, &block) 21 | Lemon.suite.dsl.test_class(target_class, &block) 22 | end 23 | 24 | # Define a module test. 25 | def Module(target_module, &block) 26 | Lemon.suite.dsl.test_module(target_module, &block) 27 | end 28 | 29 | # Define a test feature. 30 | def Feature(target, &block) 31 | Lemon.suite.dsl.test_feature(target, &block) 32 | end 33 | 34 | # 35 | #def Before(match=nil, &block) 36 | # Lemon.suite.Before(match, &block) 37 | #end 38 | 39 | # 40 | #def After(match=nil, &block) 41 | # Lemon.suite.After(match, &block) 42 | #end 43 | 44 | # 45 | #def Helper(script) 46 | # Lemon.suite.Helper(script) 47 | #end 48 | end 49 | 50 | =begin 51 | # FIXME: This is a BIG FAT HACK! For the life of me I cannot find 52 | # a way to resolve module constants included in the test cases. 53 | # Because of closure, the constant lookup goes through here, and not 54 | # the Case singleton class. So to work around we must note each test 55 | # before it is run, and reroute the missing constants. 56 | # 57 | # This sucks and it is not thread safe. If anyone know how to fix, 58 | # please let me know. See Unit#call for the other end of this hack. 59 | # 60 | def Object.const_missing(name) 61 | if unit = Lemon.test_stack.last 62 | begin 63 | (class << unit.test_case; self; end).const_get(name) 64 | rescue NameError 65 | super(name) 66 | end 67 | else 68 | super(name) 69 | end 70 | end 71 | 72 | #def Object.const_missing(name) 73 | # if unit = Lemon.test_stack.last 74 | # klass = (class << unit.test_case; self; end) 75 | # if klass.const_defined?(name) 76 | # return klass.const_get(name) 77 | # end 78 | # end 79 | # super(name) 80 | #end 81 | 82 | # Get current running test. Used for the BIG FAT HACK. 83 | def Lemon.test_stack 84 | @@test_stack ||= [] 85 | end 86 | =end 87 | 88 | -------------------------------------------------------------------------------- /work/deprecated/model/test_clause.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # 4 | class TestClause 5 | 6 | # New unit test procedure. 7 | # 8 | def initialize(scenario, description, options={}) #, &procedure) 9 | @context = scenario 10 | @description = description 11 | 12 | @type = options[:type] 13 | 14 | #@subject = options[:subject] 15 | #@aspect = options[:aspect] 16 | #@omit = options[:omit] 17 | 18 | #@procedure = procedure 19 | 20 | @tested = false 21 | end 22 | 23 | attr :type 24 | 25 | public 26 | 27 | # The case to which this test belongs. 28 | attr :context 29 | 30 | # Setup and teardown procedures. 31 | #attr :subject 32 | 33 | # 34 | #def target 35 | # context. 36 | #end 37 | 38 | # Description of test. 39 | attr :description 40 | 41 | # Test procedure, in which test assertions should be made. 42 | #attr :procedure 43 | 44 | # The before and after advice from the context. 45 | #def advice 46 | # context.advice 47 | #end 48 | 49 | # 50 | #def name ; @target ; end 51 | 52 | # Is this unit test for a class or module level method? 53 | #def function? 54 | # context.function? 55 | #end 56 | 57 | # 58 | attr_accessor :omit 59 | 60 | # 61 | def omit? 62 | @omit 63 | end 64 | 65 | # 66 | #attr_accessor :tested 67 | 68 | # 69 | #def to_s 70 | # if function? 71 | # "#{test_case}.#{target}" 72 | # else 73 | # "#{test_case}##{target}" 74 | # end 75 | #end 76 | 77 | # 78 | def to_s 79 | "#{type.to_s.capitalize} #{description}" 80 | end 81 | 82 | # 83 | def subject 84 | end 85 | 86 | # 87 | def scope 88 | context.scope 89 | end 90 | 91 | # 92 | def to_proc 93 | lambda{ call } 94 | end 95 | 96 | # 97 | #def match?(match) 98 | # match == target || match === aspect 99 | #end 100 | 101 | # 102 | def call 103 | context.run(self) do 104 | #subject.run_setup(scope) if subject 105 | scope.instance_exec(*arguments, &procedure) 106 | #subject.run_teardown(scope) if subject 107 | end 108 | end 109 | 110 | end 111 | 112 | end 113 | -------------------------------------------------------------------------------- /work/deprecated/features/generate.feature: -------------------------------------------------------------------------------- 1 | Feature: Coverage 2 | As a developer 3 | In order to improve test coverge 4 | I want to able to generate test scaffolding 5 | And limit the scaffolding to test units not already covered 6 | 7 | Scenario: Complete Example Case 8 | Given a directory named "example" 9 | Given a file named "example/lib/example.rb" with: 10 | """ 11 | class X 12 | def a; "a"; end 13 | def b; "b"; end 14 | def c; "c"; end 15 | end 16 | class Y 17 | def q; "q"; end 18 | end 19 | """ 20 | Given a file named "example/test/case_complete.rb" with: 21 | """ 22 | Covers 'example' 23 | TestCase X do 24 | Unit :a => "Returns a String" do ; X.new.a.assert.is_a?(String) ; end 25 | Unit :b => "Returns a String" do ; X.new.b.assert.is_a?(String) ; end 26 | Unit :c => "Returns a String" do ; X.new.c.assert.is_a?(String) ; end 27 | end 28 | TestCase Y do 29 | Unit :q => "Returns a String" do ; Y.new.q.assert.is_a?(String) ; end 30 | end 31 | """ 32 | When I cd to "example" 33 | And I run "lemon -g -u -Ilib test/case_complete.rb" 34 | Then the stdout should not contain "Unit :a" 35 | And the stdout should not contain "Unit :b" 36 | And the stdout should not contain "Unit :c" 37 | And the stdout should not contain "Unit :q" 38 | 39 | Scenario: Incomplete Example Case 40 | Given a directory named "example" 41 | Given a file named "example/lib/example.rb" with: 42 | """ 43 | class X 44 | def a; "a"; end 45 | def b; "b"; end 46 | def c; "c"; end 47 | end 48 | class Y 49 | def q; "q"; end 50 | end 51 | """ 52 | Given a file named "example/test/case_complete.rb" with: 53 | """ 54 | Covers 'example' 55 | TestCase X do 56 | Unit :a => "Returns a String" do ; X.new.a.assert.is_a?(String) ; end 57 | Unit :b => "Returns a String" do ; X.new.b.assert.is_a?(Fixnum) ; end 58 | Unit :d => "Returns a String" do ; X.new.d.assert.is_a?(String) ; end 59 | end 60 | """ 61 | When I cd to "example" 62 | And I run "lemon -g -u -Ilib test/case_complete.rb" 63 | Then the stdout should not contain "Unit :a" 64 | And the stdout should not contain "Unit :b" 65 | And the stdout should contain "Unit :c" 66 | 67 | -------------------------------------------------------------------------------- /work/deprecated/model/dsl/advice.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module DSL 4 | 5 | # 6 | #-- 7 | # TODO: Allow Before and After to handle before and after 8 | # concerns in addition to units? 9 | #++ 10 | module Advice 11 | 12 | # Define a _complex_ before procedure. The #before method allows 13 | # before procedures to be defined that are triggered by a match 14 | # against the unit's target method name or _aspect_ description. 15 | # This allows groups of tests to be defined that share special 16 | # setup code. 17 | # 18 | # @example 19 | # Method :puts do 20 | # Test "standard output (@stdout)" do 21 | # puts "Hello" 22 | # end 23 | # 24 | # Before /@stdout/ do 25 | # $stdout = StringIO.new 26 | # end 27 | # 28 | # After /@stdout/ do 29 | # $stdout = STDOUT 30 | # end 31 | # end 32 | # 33 | # @param [Array] matches 34 | # List of match critera that must _all_ be matched 35 | # to trigger the before procedure. 36 | # 37 | def Before(*matches, &procedure) 38 | @context.advice[:before][matches] = procedure 39 | end 40 | 41 | alias_method :before, :Before 42 | 43 | # Define a _complex_ after procedure. The #before method allows 44 | # before procedures to be defined that are triggered by a match 45 | # against the unit's target method name or _aspect_ description. 46 | # This allows groups of tests to be defined that share special 47 | # teardown code. 48 | # 49 | # @example 50 | # Method :puts do 51 | # Test "standard output (@stdout)" do 52 | # puts "Hello" 53 | # end 54 | # 55 | # Before /@stdout/ do 56 | # $stdout = StringIO.new 57 | # end 58 | # 59 | # After /@stdout/ do 60 | # $stdout = STDOUT 61 | # end 62 | # end 63 | # 64 | # @param [Array] matches 65 | # List of match critera that must _all_ be matched 66 | # to trigger the after procedure. 67 | # 68 | def After(*matches, &procedure) 69 | @context.advice[:after][matches] = procedure 70 | end 71 | 72 | alias_method :after, :After 73 | 74 | end 75 | 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /work/deprecated/rake.rb: -------------------------------------------------------------------------------- 1 | require 'rake/tasklib' 2 | 3 | module Lemon 4 | 5 | module Rake 6 | 7 | # Define a lemon test rake task. 8 | # 9 | # The `TEST` environment variable can be used to select tests 10 | # when using the task. 11 | # 12 | # TODO: The test task uses #fork. Maybe it should shell out instead? 13 | class TestTask < ::Rake::TaskLib 14 | 15 | # 16 | DEFAULT_TESTS = [ 17 | 'test/**/case_*.rb', 18 | 'test/**/*_case.rb', 19 | 'test/**/test_*.rb', 20 | 'test/**/*_test.rb' 21 | ] 22 | 23 | # 24 | attr_accessor :tests 25 | 26 | # 27 | attr_accessor :loadpath 28 | 29 | # 30 | attr_accessor :requires 31 | 32 | # 33 | attr_accessor :namespaces 34 | 35 | # 36 | attr_accessor :format 37 | 38 | # 39 | def initialize(name='lemon:test', desc="run lemon tests", &block) 40 | @name = name 41 | @desc = desc 42 | 43 | @loadpath = ['lib'] 44 | @requires = [] 45 | @tests = [ENV['TEST']] || DEFAULT_TESTS 46 | @format = nil 47 | @namespaces = [] 48 | 49 | block.call(self) 50 | 51 | define_task 52 | end 53 | 54 | # 55 | def define_task 56 | desc @desc 57 | task @name do 58 | require 'open3' 59 | 60 | @tests ||= ( 61 | if ENV['tests'] 62 | ENV['tests'].split(/[:;]/) 63 | else 64 | DEFAULT_TESTS 65 | end 66 | ) 67 | 68 | run 69 | end 70 | end 71 | 72 | # 73 | def run 74 | fork { 75 | #require 'lemon' 76 | require 'lemon/controller/test_runner' 77 | loadpath.each do |path| 78 | $LOAD_PATH.unshift(path) 79 | end 80 | requires.each do |file| 81 | require file 82 | end 83 | runner = Lemon::TestRunner.new( 84 | tests, 85 | :format => format, 86 | :namespaces => namespaces 87 | ) 88 | success = runner.run 89 | exit -1 unless success 90 | } 91 | Process.wait 92 | end 93 | 94 | # 95 | #def ruby_command 96 | # File.join(RbConfig::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) 97 | #end 98 | 99 | end 100 | 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /lib/lemon/test_module.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'lemon/test_case' 4 | require 'lemon/test_method' 5 | require 'lemon/test_class_method' 6 | 7 | # The nomenclature of a TestModule limts the focus of testing 8 | # the methods of a module. 9 | # 10 | class TestModule < TestCase 11 | 12 | # 13 | # New unit test. 14 | # 15 | def initialize(settings={}, &block) 16 | @tested = false 17 | super(settings) 18 | end 19 | 20 | # 21 | # Make sure the target is a module. 22 | # 23 | def validate_settings 24 | raise "#{@target} is not a module" unless Module === @target 25 | end 26 | 27 | # 28 | # The type of test case. 29 | # 30 | def type 31 | 'Module' 32 | end 33 | 34 | # 35 | # Gives the name of the target module. 36 | # 37 | def to_s 38 | target.to_s 39 | end 40 | 41 | # Evaluation scope for TestModule. 42 | # 43 | class DSL < TestCase::DSL 44 | 45 | # 46 | # The class for which this is a DSL context. 47 | # 48 | def context_class 49 | TestModule 50 | end 51 | 52 | # 53 | # Define a method-unit subcase for the class/module testcase. 54 | # 55 | # @example 56 | # unit :puts do 57 | # test "print message with new line to stdout" do 58 | # puts "Hello" 59 | # end 60 | # end 61 | # 62 | def unit(method, *tags, &block) 63 | return if @_omit 64 | 65 | meth = TestMethod.new( 66 | :context => @_testcase, 67 | :setup => @_setup, 68 | :skip => @_skip, 69 | :target => method.to_sym, 70 | :tags => tags, 71 | :singleton => false, 72 | &block 73 | ) 74 | @_testcase.tests << meth 75 | meth 76 | end 77 | alias :Unit :unit 78 | 79 | # 80 | # More specific nomencalture for `#unit`. 81 | # 82 | alias :method :unit 83 | alias :Method :unit 84 | 85 | # 86 | # Define a class-method unit test for this case. 87 | # 88 | def class_unit(method, *tags, &block) 89 | return if @_omit 90 | 91 | meth = TestClassMethod.new( 92 | :context => @_testcase, 93 | :setup => @_setup, 94 | :skip => @_skip, 95 | :target => method.to_sym, 96 | :tags => tags, 97 | :singleton => true, 98 | &block 99 | ) 100 | 101 | @_testcase.tests << meth 102 | 103 | meth 104 | end 105 | alias :ClassUnit :class_unit 106 | 107 | # 108 | # More specific nomencalture for `#class_unit`. 109 | # 110 | alias :class_method :class_unit 111 | alias :ClassMethod :class_unit 112 | 113 | end 114 | 115 | end 116 | 117 | end 118 | -------------------------------------------------------------------------------- /demo/coverage/02_incomplete.md: -------------------------------------------------------------------------------- 1 | ## Incomplete Coverage 2 | 3 | ### Incomplete Coverage of Public Interface 4 | 5 | Given an example script in 'lib/incomplete_example.rb' as follows: 6 | 7 | class I1 8 | def f1; "f1"; end 9 | def f2; "f2"; end 10 | def f3; "f3"; end 11 | end 12 | 13 | class I2 14 | def g1; "g1"; end 15 | protected 16 | def g2; "g2"; end 17 | private 18 | def g3; "g3"; end 19 | end 20 | 21 | class I3 22 | def h1; "h1"; end 23 | end 24 | 25 | And given a test case in 'test/incomplete_example_case.rb' as follows: 26 | 27 | Covers 'incomplete_example.rb' 28 | 29 | TestCase I1 do 30 | method :f1 do 31 | test "Returns a String" 32 | end 33 | method :f2 do 34 | test "Returns a String" 35 | end 36 | end 37 | 38 | TestCase I2 do 39 | method :x1 do 40 | test "Does not exist" 41 | end 42 | end 43 | 44 | And we get the coverage information via CoverageAnalyer. 45 | 46 | require 'lemon' 47 | 48 | tests = ['test/incomplete_example_case.rb'] 49 | 50 | coverage = Lemon::CoverageAnalyzer.new(tests, :loadpath=>'lib') 51 | 52 | Then we should see that there are 2 unconvered units, I1#f3 and I2#g1 53 | because no testcase unit was defined for them and they are both public methods. 54 | 55 | units = coverage.uncovered_units.map{ |u| u.to_s } 56 | 57 | units.assert.include?('I1#f3') 58 | units.assert.include?('I2#g1') 59 | 60 | units.size.assert == 2 61 | 62 | You might expect that 'I3#h1' would be in the uncovered units list as well, 63 | since it is a public method and no test unit covers it. However, there is 64 | no test case for I3 at all, so Lemon takes that to mean that I3 is of 65 | no interest. 66 | 67 | units.refute.include?('I3#h1') 68 | 69 | But I3 will be listed in the uncovered cases list. 70 | 71 | coverage.uncovered_cases == [I3] 72 | 73 | Note that uncovered case methods can be included in the uncovered units list 74 | by setting the +zealous+ option, which we will demonstrated later. 75 | 76 | There should still be 3 covered units, I1#f1, I1#f2 and I2#x1. 77 | 78 | coverage.covered_units.size.assert == 3 79 | 80 | units = coverage.covered_units.map{ |u| u.to_s } 81 | 82 | units.assert.include?('I1#f1') 83 | units.assert.include?('I1#f2') 84 | units.assert.include?('I2#x1') 85 | 86 | But we will not find any covered units for class I2. 87 | 88 | units.refute.include?('I2#g1') 89 | units.refute.include?('I2#g2') 90 | units.refute.include?('I2#g3') 91 | 92 | Notice also that we defined a unit for I2#x1, a method that does not exist. 93 | So it should be listed in the undefined units list. 94 | 95 | coverage.undefined_units.size.assert == 1 96 | 97 | units = coverage.undefined_units.map{ |u| u.to_s } 98 | 99 | units.assert.include?('I2#x1') 100 | 101 | -------------------------------------------------------------------------------- /lib/lemon/cli/scaffold.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | module CLI 4 | 5 | require 'lemon/cli/generate' 6 | require 'fileutils' 7 | 8 | # Scaffold Command 9 | class Scaffold < Generate 10 | 11 | # TODO: To be on the safe side, maybe require a --force option in order 12 | # to write into a test directory that already has content. 13 | 14 | # 15 | # Unlike the Generate command, the Scaffold commnad writes output 16 | # to test files. 17 | # 18 | def generate_output(render_map) 19 | render_map.each do |group, test| 20 | file = test_file(group) 21 | 22 | if File.exist?(file) 23 | append_test(file, test) 24 | else 25 | write_test(file, test) 26 | end 27 | end 28 | end 29 | 30 | # 31 | # 32 | # 33 | def command_parse(argv) 34 | option_parser.banner = "Usage: lemons scaffold [options] [files ...]" 35 | setup_options 36 | option_parser.parse!(argv) 37 | end 38 | 39 | # 40 | # 41 | # 42 | def setup_options 43 | option_output 44 | option_dryrun 45 | super 46 | end 47 | 48 | # 49 | # Output directory, default is `test`. 50 | # 51 | def output 52 | options[:output] || 'test' 53 | end 54 | 55 | # 56 | # 57 | # 58 | def dryrun? 59 | options[:dryrun] 60 | end 61 | 62 | # 63 | # Given the group name, convert it to a suitable test file name. 64 | # 65 | def test_file(group) 66 | if options[:group] == :file 67 | file = group 68 | else 69 | file = group.gsub('::', '/').downcase 70 | end 71 | 72 | dirname, basename = File.split(file) 73 | 74 | if i = dirname.index('/') 75 | dirname = dirname[i+1..-1] 76 | file = File.join(dirname, output, "case_#{basename}") 77 | else 78 | file = File.join(output, "case_#{basename}") 79 | end 80 | end 81 | 82 | # 83 | # Write test file. 84 | # 85 | def write_test(file, test) 86 | return if test.strip.empty? 87 | if dryrun? 88 | puts "[DRYRUN] write #{file}" 89 | else 90 | dir = File.dirname(file) 91 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 92 | File.open(file, 'w'){ |f| f << test.to_s } 93 | puts "write #{file}" 94 | end 95 | end 96 | 97 | # 98 | # Append tests to file. 99 | # 100 | def append_test(file, test) 101 | return if test.strip.empty? 102 | if dryrun? 103 | puts "[DRYRUN] append #{file}" 104 | else 105 | dir = File.dirname(file) 106 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 107 | File.open(file, 'a'){ |f| f << "\n" + test.to_s } 108 | puts "append #{file}" 109 | end 110 | end 111 | 112 | end 113 | 114 | end 115 | 116 | end 117 | -------------------------------------------------------------------------------- /work/reference/dsl2.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | 3 | module Lemon 4 | 5 | def self.suite 6 | @suite ||= Suite.new(Runner.new(Reporter.new)) 7 | end 8 | 9 | def self.suite=(suite) 10 | @suite = suite 11 | end 12 | 13 | class Context 14 | def initialize(desc=nil) 15 | @desc = desc 16 | @before = {} 17 | @after = {} 18 | end 19 | 20 | def description 21 | @desc 22 | end 23 | 24 | def before(which, &block) 25 | @before[which] = block if block 26 | @before[which] 27 | end 28 | 29 | def after(which, &block) 30 | @after[which] = block if block 31 | @after[which] 32 | end 33 | end 34 | 35 | class Suite 36 | def initialize(runner) 37 | @runner = runner 38 | end 39 | 40 | def before(which, &block) 41 | @runner.before(which, &block) 42 | end 43 | 44 | def after(which, &block) 45 | @runner.after(which, &block) 46 | end 47 | 48 | def unit(mod, meth, desc=nil, &block) 49 | @runner.unit(mod, meth, desc, &block) 50 | end 51 | 52 | def context(desc, &block) 53 | @runner.context(desc, &block) 54 | end 55 | end 56 | 57 | # Runner is a listner. 58 | class Runner 59 | # 60 | def initialize(*reporters) 61 | @reporters = reporters 62 | #@before = {} #Hash.new{|h,k| h[k]=[]} 63 | #@after = {} #Hash.new{|h,k| h[k]=[]} 64 | @context_stack = [Context.new] 65 | end 66 | 67 | def before(which, &block) 68 | @context_stack.last.before(which, &block) 69 | @reporters.each do |reporter| 70 | reporter.before(which, &block) 71 | end 72 | end 73 | 74 | def after(which, &block) 75 | @context_stack.last.after(which, &block) 76 | @reporters.each do |reporter| 77 | reporter.after(which, &block) 78 | end 79 | end 80 | 81 | def instance(desc, &block) 82 | @instance = Context.new(desc, &block) 83 | end 84 | 85 | def unit(mod, meth, desc=nil, &block) 86 | @reporters.each do |reporter| 87 | reporter.unit(mod, meth, desc, &block) 88 | end 89 | 90 | if @instance && block.arity != 0 91 | block.call(@instance.call) 92 | else 93 | block.call 94 | end 95 | 96 | end 97 | end 98 | 99 | class Reporter 100 | # 101 | def initialize 102 | end 103 | 104 | def before(which, &block) 105 | end 106 | 107 | def after(which, &block) 108 | end 109 | 110 | def unit(mod, meth, desc=nil, &block) 111 | puts "%s#%s %s" % [mod.name, meth.to_s, desc] 112 | end 113 | end 114 | 115 | end 116 | 117 | 118 | # 119 | def before(which, &block) 120 | Lemon.suite.before(which, &block) 121 | end 122 | 123 | # 124 | def after(which, &block) 125 | Lemon.suite.after(which, &block) 126 | end 127 | 128 | # @example 129 | # unit Example, :foo, "desciption" do 130 | # ... 131 | # end 132 | # 133 | def unit(mod, meth, desc=nil, &blk) 134 | Lemon.suite.unit(mod, meth, desc, &blk) 135 | end 136 | 137 | def context(desc, &block) 138 | Lemon.suite.context(desc, &block) 139 | end 140 | 141 | -------------------------------------------------------------------------------- /work/deprecated/command/test.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | module Command 3 | require 'lemon/command/abstract' 4 | 5 | # Lemon Test Command-line tool. 6 | class Test < Abstract 7 | require 'lemon/runner' 8 | 9 | def self.subcommand 10 | 'test' 11 | end 12 | 13 | # Initialize and run. 14 | def self.run 15 | new.run 16 | end 17 | 18 | # New Command instance. 19 | def initialize 20 | @format = nil 21 | @cover = false 22 | @requires = [] 23 | @includes = [] 24 | @namespaces = [] 25 | end 26 | 27 | # 28 | attr_accessor :format 29 | 30 | # 31 | attr_accessor :cover 32 | 33 | # Get or set librarires to pre-require. 34 | def requires(*paths) 35 | @requires.concat(paths) unless paths.empty? 36 | @requires 37 | end 38 | 39 | # Get or set paths to include in $LOAD_PATH. 40 | def includes(*paths) 41 | @includes.concat(paths) unless paths.empty? 42 | @includes 43 | end 44 | 45 | # 46 | def namespaces(*names) 47 | @namespaces.concat(names) unless names.empty? 48 | @namespaces 49 | end 50 | 51 | # Instance of OptionParser. 52 | def parser 53 | @parser ||= OptionParser.new do |opt| 54 | opt.banner = "lemon [options] [test-files ...]" 55 | opt.separator("Run unit tests.") 56 | opt.separator("OPTIONS:") 57 | #opt.on('--coverage', '-c', "include coverage informarton") do 58 | # self.cover = true 59 | #end 60 | opt.on('--verbose', '-v', "select verbose report format") do |type| 61 | self.format = :verbose 62 | end 63 | opt.on('--outline', '-o', "select outline report format") do |type| 64 | self.format = :outline 65 | end 66 | opt.on('--format', '-f [TYPE]', "select report format") do |type| 67 | self.format = type 68 | end 69 | opt.on("--namespace", "-n [NAME]", "limit testing to this namespace") do |name| 70 | namespaces(name) 71 | end 72 | opt.on("-r [FILES]" , 'library files to require') do |files| 73 | files = files.split(/[:;]/) 74 | requires(*files) 75 | end 76 | opt.on("-I [PATH]" , 'include in $LOAD_PATH') do |path| 77 | paths = path.split(/[:;]/) 78 | includes(*paths) 79 | end 80 | opt.on("--debug" , 'turn on debugging mode') do 81 | $DEBUG = true 82 | end 83 | opt.on_tail('--help', '-h', 'show this help message') do 84 | puts opt 85 | exit 86 | end 87 | end 88 | end 89 | 90 | # Run unit tests. 91 | def run 92 | parser.parse! 93 | 94 | files = ARGV.dup 95 | 96 | includes.each{ |path| $LOAD_PATH.unshift(path) } 97 | requires.each{ |path| require(path) } 98 | 99 | #suite = Lemon::Test::Suite.new(files, :cover=>cover) 100 | #runner = Lemon::Runner.new(suite, :format=>format, :cover=>cover, :namespaces=>namespaces) 101 | 102 | suite = Lemon::Test::Suite.new(files) 103 | runner = Lemon::Runner.new(suite, :format=>format, :namespaces=>namespaces) 104 | 105 | runner.run 106 | end 107 | 108 | end 109 | 110 | end 111 | end 112 | 113 | -------------------------------------------------------------------------------- /work/deprecated/model/test_feature.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/model/pending' 2 | require 'lemon/model/test_context' 3 | require 'lemon/model/test_advice' 4 | require 'lemon/model/test_scenario' 5 | 6 | module Lemon 7 | 8 | # The TestFeature ... 9 | # 10 | # * `tests` are _scenarios_, 11 | # * `advice` are _given_, _when_ and _then_ rules. 12 | # 13 | class TestFeature < TestCase 14 | 15 | # 16 | def initialize(context, settings={}, &block) 17 | @story = [] 18 | super(context, settings, &block) 19 | end 20 | 21 | ## This has to be redefined in each subclass to pick 22 | ## up there respective DSL classes. 23 | #def evaluate(&block) 24 | # @dsl = DSL.new(self, &block) 25 | #end 26 | 27 | attr :story 28 | 29 | # Feature scenarios are tests. 30 | alias_method :scenarios, :tests 31 | 32 | # 33 | def to_s 34 | "Feature #{target}" 35 | end 36 | 37 | # 38 | def to_s #description 39 | (["Feature: #{target}"] + story).join("\n ") 40 | end 41 | 42 | # Run test in the context of this case. 43 | # 44 | # @param [TestProc] test 45 | # The test procedure instance to run. 46 | # 47 | def run(&block) 48 | end 49 | 50 | # 51 | class DSL < Module 52 | 53 | # 54 | def initialize(feature, &code) 55 | @feature = feature 56 | 57 | module_eval(&code) 58 | end 59 | 60 | # 61 | def To(description) 62 | @feature.story << "To " + description 63 | end 64 | 65 | # 66 | def As(description) 67 | @feature.story << "As " + description 68 | end 69 | 70 | # 71 | def We(description) 72 | @feature.story << "We " + description 73 | end 74 | 75 | # 76 | def Scenario(description, &procedure) 77 | scenario = TestScenario.new(@feature, description, &procedure) 78 | @feature.scenarios << scenario 79 | scenario 80 | end 81 | alias_method :scenario, :Scenario 82 | 83 | # Omit a scenario from test runs. 84 | # 85 | # omit unit :foo do 86 | # # ... 87 | # end 88 | # 89 | def Omit(scenario) 90 | scenario.omit = true 91 | end 92 | alias_method :omit, :Omit 93 | 94 | # Given ... 95 | # 96 | # @param [String] description 97 | # A brief description of the _given_ criteria. 98 | # 99 | def Given(description, &procedure) 100 | @feature.advice[:given][description] = procedure 101 | end 102 | alias_method :given, :Given 103 | 104 | # When ... 105 | # 106 | # @param [String] description 107 | # A brief description of the _when_ criteria. 108 | # 109 | def When(description, &procedure) 110 | @feature.advice[:when][description] = procedure 111 | end 112 | alias_method :wence, :When 113 | 114 | # Then ... 115 | # 116 | # @param [String] description 117 | # A brief description of the _then_ criteria. 118 | # 119 | def Then(description, &procedure) 120 | @feature.advice[:then][description] = procedure 121 | end 122 | alias_method :hence, :Then 123 | 124 | end 125 | 126 | end 127 | 128 | end 129 | -------------------------------------------------------------------------------- /work/deprecated/command/coverage.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | module Command 3 | require 'lemon/command/abstract' 4 | 5 | # Lemon Coverage Command-line tool. 6 | class Coverage < Abstract 7 | require 'yaml' 8 | require 'lemon/coverage' 9 | 10 | def self.subcommand 11 | 'coverage' #['-c', '--coverage'] 12 | end 13 | 14 | # Initialize and run. 15 | def self.run 16 | new.run 17 | end 18 | 19 | # New Command instance. 20 | def initialize 21 | @format = nil 22 | @requires = [] 23 | @includes = [] 24 | @namespaces = [] 25 | @public_only = false 26 | end 27 | 28 | # 29 | attr_accessor :format 30 | 31 | # 32 | attr_accessor :public_only 33 | 34 | # 35 | def public_only? 36 | @public_only 37 | end 38 | 39 | # Get or set librarires to pre-require. 40 | def requires(*paths) 41 | @requires.concat(paths) unless paths.empty? 42 | @requires 43 | end 44 | 45 | # Get or set paths to include in $LOAD_PATH. 46 | def includes(*paths) 47 | @includes.concat(paths) unless paths.empty? 48 | @includes 49 | end 50 | 51 | # Get or set paths to include in $LOAD_PATH. 52 | def namespaces(*names) 53 | @namespaces.concat(names) unless names.empty? 54 | @namespaces 55 | end 56 | 57 | # Instance of OptionParser. 58 | def parser 59 | @parser ||= OptionParser.new do |opt| 60 | opt.banner = "lemon coverage [OPTIONS]" 61 | opt.separator("Produce test coverage report.") 62 | opt.on('--verbose', '-v', "select verbose report format") do |type| 63 | self.format = :verbose 64 | end 65 | #opt.on('--outline', '-o', "select outline report format") do |type| 66 | # self.format = :outline 67 | #end 68 | #opt.on('--format', '-f [TYPE]', "select report format") do |type| 69 | # self.format = type 70 | #end 71 | opt.on('--namespace', '-n [NAME]', "limit coverage to this namespace") do |name| 72 | namespaces(name) 73 | end 74 | opt.on('--public', '-p', "only include public methods") do 75 | self.public_only = true 76 | end 77 | opt.on("-r [FILES]" , 'library files to require') do |files| 78 | files = files.split(/[:;]/) 79 | requires(*files) 80 | end 81 | opt.on("-I [PATH]" , 'include in $LOAD_PATH') do |path| 82 | path = path.split(/[:;]/) 83 | includes(*path) 84 | end 85 | opt.on("--debug" , 'turn on debugging mode') do 86 | $DEBUG = true 87 | end 88 | opt.on_tail('--help', '-h', 'show this help message') do 89 | puts opt 90 | exit 91 | end 92 | end 93 | end 94 | 95 | # 96 | def run 97 | parser.parse! 98 | 99 | test_files = ARGV.dup 100 | load_files = [] 101 | 102 | includes.each{ |path| $LOAD_PATH.unshift(path) } 103 | requires.each{ |path| require(path) } 104 | 105 | suite = Lemon::Test::Suite.new(test_files, :cover=>true) 106 | coverage = Lemon::Coverage.new(suite, namespaces, :public=>public_only?) 107 | 108 | coverage.format(format) 109 | end 110 | 111 | end 112 | 113 | end 114 | end 115 | 116 | -------------------------------------------------------------------------------- /work/deprecated/command/generate.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | module Command 3 | require 'lemon/command/abstract' 4 | 5 | # Lemon Generate Command-line tool. 6 | class Generate < Abstract 7 | require 'lemon/coverage' 8 | 9 | # 10 | def self.subcommand 11 | 'generate' #['-g', '--generate'] 12 | end 13 | 14 | # Initialize and run. 15 | def self.run 16 | new.run 17 | end 18 | 19 | # New Command instance. 20 | def initialize 21 | @requires = [] 22 | @includes = [] 23 | @namespaces = [] 24 | @public_only = false 25 | @uncovered = false 26 | end 27 | 28 | # TODO: Support output ? perhaps complex scaffolding 29 | #attr_accessor :output 30 | 31 | # 32 | attr_accessor :public_only 33 | 34 | # 35 | attr_accessor :uncovered 36 | 37 | # 38 | def public_only? 39 | @public_only 40 | end 41 | 42 | # 43 | def uncovered_only? 44 | @uncovered 45 | end 46 | 47 | # Get or set librarires to pre-require. 48 | def requires(*paths) 49 | @requires.concat(paths) unless paths.empty? 50 | @requires 51 | end 52 | 53 | # Get or set paths to include in $LOAD_PATH. 54 | def includes(*paths) 55 | @includes.concat(paths) unless paths.empty? 56 | @includes 57 | end 58 | 59 | # 60 | def namespaces(*names) 61 | @namespaces.concat(names) unless names.empty? 62 | @namespaces 63 | end 64 | 65 | # Instance of OptionParser. 66 | def parser 67 | @parser ||= OptionParser.new do |opt| 68 | opt.banner = "lemon generate [OPTIONS]" 69 | opt.separator("Generate unit test scaffolding.") 70 | opt.on("--namespace", "-n [NAME]", "limit tests to this namespace") do |name| 71 | namespaces(name) 72 | end 73 | opt.on("--public", "-p", "only include public methods") do 74 | self.public_only = true 75 | end 76 | opt.on("--uncovered", "-u", "only include uncovered methods") do 77 | self.uncovered = true 78 | end 79 | #opt.on("--output", "-o [PATH]", "output directory") do |path| 80 | # self.output = path 81 | #end 82 | opt.on("-r [FILES]" , "library files to require") do |files| 83 | files = files.split(/[:;]/) 84 | requires(*files) 85 | end 86 | opt.on("-I [PATH]" , "include in $LOAD_PATH") do |path| 87 | path = path.split(/[:;]/) 88 | includes(*path) 89 | end 90 | opt.on("--debug" , "turn on debugging mode") do 91 | $DEBUG = true 92 | end 93 | opt.on_tail("--help", "-h", "show this help message") do 94 | puts opt 95 | exit 96 | end 97 | end 98 | end 99 | 100 | # Generate test skeletons. 101 | def run 102 | parser.parse! 103 | 104 | test_files = ARGV.dup 105 | 106 | includes.each{ |path| $LOAD_PATH.unshift(path) } 107 | requires.each{ |path| require(path) } 108 | 109 | suite = Lemon::Test::Suite.new(test_files, :cover=>true) 110 | cover = Lemon::Coverage.new(suite, namespaces, :public=>public_only?) 111 | #cover = Lemon::Coverage.new([], namespaces, :public=>public_only?, :uncovered=>uncovered_only?) 112 | 113 | if uncovered_only? 114 | puts cover.generate_uncovered #(output) 115 | else 116 | puts cover.generate #(output) 117 | end 118 | end 119 | 120 | end 121 | 122 | end 123 | end 124 | 125 | -------------------------------------------------------------------------------- /work/deprecated/model/test_scenario.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/model/test_clause' 2 | 3 | module Lemon 4 | 5 | # 6 | class TestScenario < TestCase 7 | 8 | # 9 | def initialize(feature, summary, options={}, &block) 10 | @feature = feature 11 | @summary = summary 12 | 13 | @tests = [] 14 | 15 | evaluate(&block) 16 | end 17 | 18 | # 19 | def evaluate(&procedure) 20 | @dsl = DSL.new(self, &procedure) 21 | end 22 | 23 | # 24 | attr :feature 25 | 26 | # 27 | attr :dsl 28 | 29 | attr_accessor :omit 30 | 31 | # 32 | def omit? 33 | @omit 34 | end 35 | 36 | # 37 | def to_s 38 | "Scenario: #{@summary}" 39 | end 40 | 41 | def subject 42 | 43 | end 44 | 45 | # 46 | def run(clause) 47 | type = clause.type 48 | desc = clause.description 49 | feature.advice[type].each do |mask, proc| 50 | if md = match_regexp(mask).match(desc) 51 | scope.instance_exec(*md[1..-1], &proc) 52 | end 53 | end 54 | end 55 | 56 | # 57 | #-- 58 | # TODO: Change so that the scope is the DSL 59 | # and includes the DSL of the context? 60 | #++ 61 | def scope 62 | @scope ||= ( 63 | #if feature 64 | # scope = feature.scope || Object.new 65 | # scope.extend(dsl) 66 | #else 67 | scope = Object.new 68 | scope.extend(dsl) 69 | #end 70 | ) 71 | end 72 | 73 | # 74 | #def find 75 | # features.clauses[@type].find{ |c| c =~ @description } 76 | #end 77 | 78 | # Convert matching string into a regular expression. If the string 79 | # contains double parenthesis, such as ((.*?)), then the text within 80 | # them is treated as in regular expression and kept verbatium. 81 | # 82 | # TODO: Better way to isolate regexp. Maybe ?:(.*?) or /(.*?)/. 83 | # 84 | def match_regexp(str) 85 | str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x| 86 | x =~ /\A\(\((.*)\)\)\Z/ ? $1 : Regexp.escape(x) 87 | }.join 88 | str = str.gsub(/\\\s+/, '\s+') 89 | Regexp.new(str, Regexp::IGNORECASE) 90 | end 91 | 92 | 93 | # TODO: Need to ensure the correct order of Given, When, Then. 94 | class DSL < Module 95 | 96 | # 97 | def initialize(scenario, &code) 98 | @scenario = scenario 99 | 100 | module_eval(&code) 101 | end 102 | 103 | # Given ... 104 | # 105 | # @param [String] description 106 | # A brief description of what the setup procedure sets-up. 107 | # 108 | def Given(description) 109 | @scenario.tests << TestClause.new(@scenario, description, :type=>:given) 110 | end 111 | alias_method :given, :Given 112 | 113 | # When ... 114 | # 115 | # @param [String] description 116 | # A brief description of what the setup procedure sets-up. 117 | # 118 | def When(description) 119 | @scenario.tests << TestClause.new(@scenario, description, :type=>:when) 120 | end 121 | alias_method :wence, :When 122 | 123 | # Then ... 124 | # 125 | # @param [String] description 126 | # A brief description of what the setup procedure sets-up. 127 | # 128 | def Then(description) 129 | @scenario.tests << TestClause.new(@scenario, description, :type=>:then) 130 | end 131 | alias_method :hence, :Then 132 | 133 | end 134 | 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /lib/lemon/test_proc.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Test procedure. 4 | # 5 | class TestProc 6 | 7 | # New test procedure. 8 | # 9 | def initialize(settings={}, &procedure) 10 | @context = settings[:context] 11 | @setup = settings[:setup] 12 | @label = settings[:label] 13 | @skip = settings[:skip] 14 | @tags = settings[:tags] 15 | 16 | @procedure = procedure 17 | 18 | @tested = false 19 | end 20 | 21 | public 22 | 23 | # The parent case to which this test belongs. 24 | attr :context 25 | 26 | # Setup and teardown procedures. 27 | attr :setup 28 | 29 | # Description of test. 30 | attr :label 31 | 32 | # Test procedure, in which test assertions should be made. 33 | attr :procedure 34 | 35 | # 36 | #attr :caller 37 | 38 | # 39 | # Target method of context. 40 | # 41 | def target 42 | context.target 43 | end 44 | 45 | # 46 | attr_accessor :skip 47 | 48 | # 49 | # Don't run test? 50 | # 51 | def skip? 52 | @skip 53 | end 54 | 55 | # 56 | # 57 | # 58 | def tags 59 | @tags 60 | end 61 | 62 | # Has this test been executed? 63 | attr_accessor :tested 64 | 65 | # 66 | # Test label. 67 | # 68 | def to_s 69 | label.to_s 70 | end 71 | 72 | alias_method :name, :to_s 73 | 74 | # 75 | # Ruby Test looks for #topic as the description of test setup. 76 | # 77 | # @todo This may be deprecated in future RubyTest. 78 | # 79 | def topic 80 | setup.to_s 81 | end 82 | 83 | # 84 | #def description 85 | # if singleton? 86 | # #"#{test_case} .#{target} #{aspect}" 87 | # "#{test_case}.#{target} #{context} #{aspect}".strip 88 | # else 89 | # a = /^[aeiou]/i =~ test_case.to_s ? 'An' : 'A' 90 | # #"#{a} #{test_case} receiving ##{target} #{aspect}" 91 | # "#{test_case}##{target} #{context} #{aspect}".strip 92 | # end 93 | #end 94 | 95 | # 96 | #def name 97 | # if singleton? 98 | # "#{test_case}.#{target}" 99 | # else 100 | # "#{test_case}##{target}" 101 | # end 102 | #end 103 | 104 | # TODO: handle parameterized tests 105 | def arguments 106 | [] 107 | end 108 | 109 | # 110 | # 111 | # 112 | def to_proc 113 | lambda do 114 | call 115 | end 116 | end 117 | 118 | # 119 | # 120 | # 121 | def match?(match) 122 | match == target || match === description 123 | end 124 | 125 | # 126 | # Run the test. 127 | # 128 | def call 129 | context.run(self) do 130 | setup.run_setup(scope) if setup 131 | scope.instance_exec(*arguments, &procedure) 132 | setup.run_teardown(scope) if setup 133 | end 134 | end 135 | 136 | # 137 | # 138 | # 139 | def scope 140 | context.scope 141 | end 142 | 143 | =begin 144 | # The file_and_line method returns the file name and line number of 145 | # the caller created upon initialization of this object. 146 | # 147 | # This method is cached. 148 | # 149 | # Examples 150 | # file_and_line #=> ['foo_test.rb', 123] 151 | # 152 | # Returns Array of file name and line number of caller. 153 | def source_location 154 | @file_and_line ||= ( 155 | line = caller[0] 156 | i = line.rindex(':in') 157 | line = i ? line[0...i] : line 158 | f, l = File.basename(line).split(':') 159 | [f, l] 160 | ) 161 | end 162 | =end 163 | 164 | end 165 | 166 | end 167 | -------------------------------------------------------------------------------- /demo/coverage/01_complete.md: -------------------------------------------------------------------------------- 1 | ## Complete Coverage 2 | 3 | ### Complete Coverage of Public Interface 4 | 5 | Given an example script in 'lib/complete_example.rb' as follows: 6 | 7 | class C1 8 | def f1; "f1"; end 9 | def f2; "f2"; end 10 | def f3; "f3"; end 11 | end 12 | 13 | class C2 14 | def g1; "g1"; end 15 | protected 16 | def g2; "g2"; end 17 | private 18 | def g3; "g3"; end 19 | end 20 | 21 | And given a test case in 'test/complete_example_case.rb' as follows: 22 | 23 | Covers 'complete_example.rb' 24 | 25 | TestCase C1 do 26 | method :f1 do 27 | test "Returns a String" 28 | end 29 | method :f2 do 30 | test "Returns a String" 31 | end 32 | method :f3 do 33 | test "Returns a String" 34 | end 35 | end 36 | 37 | TestCase C2 do 38 | method :g1 do 39 | test "Returns a String" 40 | end 41 | end 42 | 43 | And we get the coverage information via CoverageAnalyer. 44 | 45 | require 'lemon' 46 | 47 | tests = ['test/complete_example_case.rb'] 48 | 49 | coverage = Lemon::CoverageAnalyzer.new(tests, :loadpath=>'lib') 50 | 51 | Then we should see that there are no uncovered units. 52 | 53 | coverage.uncovered_units.assert == [] 54 | 55 | And there should be 4 covered units, 56 | 57 | coverage.covered_units.size.assert == 4 58 | 59 | one for each public class and method. 60 | 61 | units = coverage.covered_units.map{ |u| u.to_s } 62 | 63 | units.assert.include?('C1#f1') 64 | units.assert.include?('C1#f2') 65 | units.assert.include?('C1#f3') 66 | 67 | units.assert.include?('C2#g1') 68 | 69 | There should not be any coverage for private and protected methods. 70 | 71 | units.refute.include?('C2#g2') 72 | units.refute.include?('C2#g3') 73 | 74 | In addition there should be no uncovered_cases or undefined_units. 75 | 76 | coverage.undefined_units.assert = [] 77 | coverage.uncovered_cases.assert = [] 78 | 79 | ### Including Private and Protected Methods 80 | 81 | We will use the same example classes as above, but in this case we will 82 | add coverage for private and protected methods as well, given a test case 83 | in 'test/complete_example_case.rb' as follows: 84 | 85 | Covers 'complete_example.rb' 86 | 87 | TestCase C1 do 88 | method :f1 do 89 | test "Returns a String" 90 | end 91 | method :f2 do 92 | test "Returns a String" 93 | end 94 | method :f3 do 95 | test "Returns a String" 96 | end 97 | end 98 | 99 | TestCase C2 do 100 | method :g1 do 101 | test "Returns a String" 102 | end 103 | method :g2 do 104 | test "Returns a String" 105 | end 106 | method :g3 do 107 | test "Returns a String" 108 | end 109 | end 110 | 111 | And we get the coverage information via CoverageAnalyer. 112 | 113 | require 'lemon' 114 | 115 | tests = ['test/complete_example_case.rb'] 116 | 117 | coverage = Lemon::CoverageAnalyzer.new(tests, :loadpath=>'lib', :private=>true) 118 | 119 | Notice the use of the +private+ option. This will add private and protected 120 | methods to the coverage analysis. 121 | 122 | Then we should see that there are no uncovered units. 123 | 124 | coverage.uncovered_units.assert == [] 125 | 126 | And there should be 6 covered units, 127 | 128 | coverage.covered_units.size.assert == 6 129 | 130 | one for each class and method. 131 | 132 | units = coverage.covered_units.map{ |u| u.to_s } 133 | 134 | units.assert.include?('C1#f1') 135 | units.assert.include?('C1#f2') 136 | units.assert.include?('C1#f3') 137 | 138 | units.assert.include?('C2#g1') 139 | units.assert.include?('C2#g2') 140 | units.assert.include?('C2#g3') 141 | 142 | In addition there should be no uncovered cases or undefined units. 143 | 144 | coverage.undefined_units.assert = [] 145 | coverage.uncovered_cases.assert = [] 146 | 147 | -------------------------------------------------------------------------------- /lib/lemon/cli/base.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'optparse' 4 | 5 | module CLI 6 | 7 | # Base class for all commands. 8 | class Base 9 | 10 | # 11 | def self.run(argv) 12 | new.run(argv) 13 | end 14 | 15 | # 16 | def options 17 | @options 18 | end 19 | 20 | # 21 | def run(argv) 22 | begin 23 | command_parse(argv) 24 | command_run(argv) 25 | #rescue => err 26 | # raise err if $DEBUG 27 | # $stderr.puts('ERROR: ' + err.to_s) 28 | end 29 | end 30 | 31 | private 32 | 33 | # 34 | # Initialize new command instance. This will be overriden in subclasses. 35 | # 36 | def initialize(argv=ARGV) 37 | @options = {} 38 | end 39 | 40 | # 41 | # Parse command line argument. This is a no-op as it will be overridden 42 | # in subclasses. 43 | # 44 | def command_parse(argv) 45 | end 46 | 47 | # 48 | def option_parser 49 | @option_parser ||= ( 50 | OptionParser.new do |opt| 51 | opt.on_tail("--[no-]ansi" , 'turn on/off ANIS colors') do |v| 52 | $ansi = v 53 | end 54 | opt.on_tail("--debug" , 'turn on debugging mode') do 55 | $DEBUG = true 56 | end 57 | opt.on_tail("--about" , 'display information about lemon') do 58 | puts "Lemon v#{Lemon::VERSION}" 59 | puts "#{Lemon::COPYRIGHT}" 60 | exit 61 | end 62 | opt.on_tail('-h', '--help', 'display help (also try ` --help`)') do 63 | puts opt 64 | exit 65 | end 66 | end 67 | ) 68 | end 69 | 70 | # -n --namespace 71 | def option_namespaces 72 | option_parser.on('-n', '--namespace NAME', 'add a namespace to output') do |name| 73 | options[:namespaces] ||= [] 74 | options[:namespaces] << name 75 | end 76 | end 77 | 78 | #def option_framework 79 | # option_parser.on('-f', '--framework NAME', 'framework syntax to output') do |name| 80 | # options[:framework] = name.to_sym 81 | # end 82 | #end 83 | 84 | # -f --format 85 | def option_format 86 | option_parser.on('-f', '--format NAME', 'output format') do |name| 87 | options[:format] = name 88 | end 89 | end 90 | 91 | # -v --verbose 92 | def option_verbose 93 | option_parser.on('-v', '--verbose', 'shortcut for `-f verbose`') do |name| 94 | options[:format] = 'verbose' 95 | end 96 | end 97 | 98 | # -c --covered, -u --uncovered and -a --all 99 | def option_coverage 100 | option_parser.on('-c', '--covered', 'include covered units') do 101 | if options[:coverage] == :uncovered 102 | options[:coverage] = :all 103 | else 104 | options[:coverage] = :covered 105 | end 106 | end 107 | option_parser.on('-u', '--uncovered', 'include only uncovered units') do 108 | if options[:coverage] == :covered 109 | options[:coverage] = :all 110 | else 111 | options[:coverage] = :uncovered 112 | end 113 | end 114 | option_parser.on('-a', '--all', 'include all namespaces and units') do 115 | options[:coverage] = :all 116 | end 117 | end 118 | 119 | # -p --private 120 | def option_private 121 | option_parser.on('-p', '--private', 'include private and protected methods') do 122 | options[:private] = true 123 | end 124 | end 125 | 126 | # -z --zealous 127 | def option_zealous 128 | option_parser.on('-z', '--zealous', 'include undefined case methods') do 129 | options[:zealous] = true 130 | end 131 | end 132 | 133 | # -o --output 134 | def option_output 135 | option_parser.on('-o', '--output DIRECTORY', 'output directory') do |dir| 136 | options[:output] = dir 137 | end 138 | end 139 | 140 | # -I 141 | def option_loadpath 142 | option_parser.on("-I PATH" , 'add directory to $LOAD_PATH') do |path| 143 | paths = path.split(/[:;]/) 144 | options[:loadpath] ||= [] 145 | options[:loadpath].concat(paths) 146 | end 147 | end 148 | 149 | # -r 150 | def option_requires 151 | option_parser.on("-r FILE" , 'require file(s) (before doing anything else)') do |files| 152 | files = files.split(/[:;]/) 153 | options[:requires] ||= [] 154 | options[:requires].concat(files) 155 | end 156 | end 157 | 158 | # --dryrun 159 | def option_dryrun 160 | option_parser.on('--dryrun', 'no disk writes') do 161 | options[:dryrun] = true 162 | end 163 | end 164 | 165 | end 166 | 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /lib/lemon/test_method.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'lemon/test_case' 4 | require 'lemon/test_proc' 5 | 6 | # The TestMethod class is a special TestCase that requires 7 | # a particular target method be tested. 8 | # 9 | class TestMethod < TestCase 10 | 11 | # 12 | # New unit test. 13 | # 14 | def initialize(settings={}, &block) 15 | @tested = false 16 | super(settings) 17 | end 18 | 19 | # 20 | # Validate that a context and target method have been supplied. 21 | # 22 | def validate_settings 23 | raise "method test has no module or class context" unless @context 24 | raise "#{@target} is not a method name" unless Symbol === @target 25 | end 26 | 27 | # 28 | # Description of the type of test case. 29 | # 30 | def type 31 | 'Method' 32 | end 33 | 34 | # 35 | # Used to make sure the the method has been tested, or not. 36 | # 37 | attr_accessor :tested 38 | 39 | # 40 | # If class method, returns target method's name prefixed with double colons. 41 | # If instance method, then returns target method's name prefixed with hash 42 | # character. 43 | # 44 | def name 45 | "##{target}" 46 | end 47 | 48 | # TODO: If sub-cases are to be supported than we need to incorporate 49 | # the label into to_s. 50 | 51 | # 52 | # Returns the prefixed method name. 53 | # 54 | def to_s 55 | "##{target}" 56 | end 57 | 58 | # 59 | # Returns the fully qulaified name of the target method. This is 60 | # the standard interface used by Ruby Test. 61 | # 62 | def unit 63 | "#{context}##{target}" 64 | end 65 | 66 | # 67 | # Run test in the context of this case. Notice that #run for 68 | # TestMethod is more complex than a general TestCase. This is 69 | # to ensure that the target method is invoked during the course 70 | # of the test. 71 | # 72 | # @param [TestProc] test 73 | # The test procedure instance to run. 74 | # 75 | # @yield 76 | # The procedure for running the test. 77 | # 78 | def run(test, &block) 79 | target = self.target 80 | 81 | raise_pending(test.procedure) unless test.procedure 82 | 83 | begin 84 | target_class.class_eval do 85 | alias_method "_lemon_#{target}", target 86 | define_method(target) do |*a,&b| 87 | test.tested = true 88 | __send__("_lemon_#{target}",*a,&b) 89 | end 90 | end 91 | rescue => error 92 | Kernel.eval <<-END, test.to_proc.binding 93 | raise #{error.class}, "#{target} not tested" 94 | END 95 | end 96 | 97 | begin 98 | super(test, &block) 99 | #block.call 100 | 101 | ensure 102 | target_class.class_eval %{ 103 | alias_method "#{target}", "_lemon_#{target}" 104 | } 105 | end 106 | 107 | raise_pending(test.procedure) unless test.tested 108 | end 109 | 110 | # 111 | # 112 | # 113 | def raise_pending(procedure) 114 | if RUBY_VERSION < '1.9' 115 | Kernel.eval %[raise NotImplementedError, "#{target} not tested"], procedure 116 | else 117 | Kernel.eval %[raise NotImplementedError, "#{target} not tested"], procedure.binding 118 | end 119 | end 120 | 121 | # 122 | # The target class. 123 | # 124 | def target_class 125 | @target_class ||= context.target 126 | end 127 | 128 | # 129 | # 130 | # 131 | def class_method? 132 | false 133 | end 134 | 135 | # Scope for evaluating method test definitions. 136 | # 137 | class DSL < TestCase::DSL 138 | 139 | # 140 | # The class for which this is a DSL context. 141 | # 142 | def context_class 143 | TestMethod 144 | end 145 | 146 | # 147 | # Define a unit test for this case. 148 | # 149 | # @example 150 | # test "print message with new line to stdout" do 151 | # puts "Hello" 152 | # end 153 | # 154 | def test(label=nil, *tags, &block) 155 | return if @_omit 156 | 157 | test = TestProc.new( 158 | :context => @_testcase, 159 | :setup => @_setup, 160 | :skip => @_skip, 161 | :label => label, 162 | :tags => tags, 163 | &block 164 | ) 165 | 166 | @_testcase.tests << test 167 | 168 | test 169 | end 170 | 171 | # 172 | # Create a sub-case of the method case. 173 | # 174 | def context(label, *tags, &block) 175 | return if @_omit 176 | 177 | @_testcase.tests << TestMethod.new( 178 | :context => @_testcase, 179 | :target => @_testcase.target, 180 | :setup => @_setup, 181 | :skip => @_skip, 182 | :label => label, 183 | :tags => tags, 184 | &block 185 | ) 186 | end 187 | 188 | # Capitialized term. 189 | alias :Test :test 190 | alias :Context :context 191 | 192 | end 193 | 194 | end 195 | 196 | end 197 | -------------------------------------------------------------------------------- /lib/lemon/coverage/snapshot.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | # Snapshot is used to record the "unit picture" of the system 4 | # at a given moment. 5 | class Snapshot 6 | 7 | include Enumerable 8 | 9 | # 10 | def self.capture(namespaces=nil) 11 | o = new 12 | o.capture(namespaces) 13 | o 14 | end 15 | 16 | # 17 | attr :units 18 | 19 | # 20 | def initialize(units=[]) 21 | @units = units 22 | end 23 | 24 | # 25 | def each(&block) 26 | units.each(&block) 27 | end 28 | 29 | # 30 | def size 31 | @units.size 32 | end 33 | 34 | # 35 | def reset 36 | each{ |u| u.covered = false } 37 | end 38 | 39 | # Select units by namespace (i.e. module or class). 40 | #def [](mod) 41 | # @units.select{ |u| u.namespace == mod } 42 | #end 43 | 44 | # 45 | def capture(namespaces=nil) 46 | @units = [] 47 | ObjectSpace.each_object(Module) do |mod| 48 | next if mod.nil? or mod.name.nil? or mod.name.empty? 49 | #next if namespaces and !namespaces.any?{ |ns| /^#{ns}(::|$)/ =~ mod.to_s } 50 | next if namespaces and !namespaces.any?{ |ns| ns.to_s == mod.to_s } 51 | capture_namespace(mod) 52 | end 53 | end 54 | 55 | # 56 | def capture_namespace(mod) 57 | ['', 'protected_', 'private_'].each do |access| 58 | methods = mod.__send__("#{access}instance_methods", false) 59 | #methods -= Object.__send__("#{access}instance_methods", true) 60 | methods.each do |method| 61 | @units << Unit.new(mod, method, :access=>access) 62 | end 63 | 64 | methods = mod.__send__("#{access}methods", false) 65 | #methods -= Object.__send__("#{access}methods", true) 66 | methods.each do |method| 67 | @units << Unit.new(mod, method, :access=>access, :singleton=>true) 68 | end 69 | end 70 | return @units 71 | end 72 | 73 | # 74 | def to_a 75 | @units 76 | end 77 | 78 | # 79 | def public_units 80 | @units.select{ |u| u.public? } 81 | end 82 | 83 | # 84 | def -(other) 85 | Snapshot.new(units - other.units) 86 | end 87 | 88 | # 89 | def +(other) 90 | Snapshot.new(units + other.units) 91 | end 92 | 93 | # 94 | def <<(other) 95 | @units.concat(other.units) 96 | end 97 | 98 | # Snapshot Unit encapsulates a method and it's various characteristics. 99 | class Unit 100 | 101 | # Clsss or module. 102 | attr :target 103 | 104 | # Method name. 105 | attr :method 106 | 107 | # Is the method a "class method", rather than an instance method. 108 | attr :singleton 109 | 110 | def initialize(target, method, props={}) 111 | @target = target 112 | @method = method.to_sym 113 | @singleton = props[:singleton] ? true : false 114 | @covered = props[:covered] 115 | 116 | if @singleton 117 | @private = @target.private_methods.find{ |m| m.to_sym == @method } 118 | @protected = @target.protected_methods.find{ |m| m.to_sym == @method } 119 | else 120 | @private = @target.private_instance_methods.find{ |m| m.to_sym == @method } 121 | @protected = @target.protected_instance_methods.find{ |m| m.to_sym == @method } 122 | end 123 | end 124 | 125 | # Can be used to flag a unit as covered. 126 | attr_accessor :covered 127 | 128 | # Alternate name for target. 129 | def namespace 130 | @target 131 | end 132 | 133 | # 134 | def singleton? 135 | @singleton 136 | end 137 | 138 | # Method access is public? 139 | def public? 140 | !(@private or @protected) 141 | end 142 | 143 | # Method access is public? 144 | def private? 145 | @private 146 | end 147 | 148 | # 149 | def protected? 150 | @protected 151 | end 152 | 153 | # 154 | def access 155 | return :private if private? 156 | return :protected if protected? 157 | :public 158 | end 159 | 160 | # Marked as covered? 161 | def covered? 162 | @covered 163 | end 164 | 165 | # 166 | def hash 167 | @target.hash ^ @method.hash ^ @singleton.hash 168 | end 169 | 170 | # 171 | def to_s 172 | if @singleton 173 | "#{@target}.#{@method}" 174 | else 175 | "#{@target}##{@method}" 176 | end 177 | end 178 | alias to_str to_s 179 | 180 | def eql?(other) 181 | return false unless Unit === other 182 | return false unless target == other.target 183 | return false unless method == other.method 184 | return false unless singleton == other.singleton 185 | return true 186 | end 187 | 188 | def inspect 189 | "#{target}#{singleton ? '.' : '#'}#{method}" 190 | end 191 | 192 | def <=>(other) 193 | c = (target.name <=> other.target.name) 194 | return c unless c == 0 195 | return -1 if singleton && !other.singleton 196 | return 1 if !singleton && other.singleton 197 | method.to_s <=> other.method.to_s 198 | end 199 | end 200 | 201 | end 202 | 203 | end 204 | -------------------------------------------------------------------------------- /lib/lemon/coverage/generator.rb: -------------------------------------------------------------------------------- 1 | module Lemon 2 | 3 | require 'lemon/coverage/analyzer' 4 | require 'lemon/coverage/source_parser' 5 | 6 | # TODO: Add option to add `require ` for each test file genetated. 7 | 8 | # Test Scaffold Generator. 9 | # 10 | class Generator 11 | 12 | # New Scaffold Generator. 13 | # 14 | # @option options [Array] :files 15 | # Ruby scripts for which to generate tests. 16 | # 17 | # @option options [Array] :tests 18 | # Test files that already exist. 19 | # 20 | # @option options [Array] :namespaces 21 | # List of class/module names to limit scaffolding. 22 | # 23 | # @option options [Boolean] :private 24 | # Include private methods in scaffolding. 25 | # 26 | # @option options [Symbol] :group 27 | # Group by `:case` or by `:file`. 28 | # 29 | def initialize(options={}) 30 | @files = options[:files] || [] 31 | @tests = options[:tests] || [] 32 | @group = options[:group] || :case 33 | 34 | @namespaces = options[:namespaces] 35 | #@coverage = options[:coverage] 36 | @private = options[:private] 37 | @caps = options[:caps] 38 | 39 | #if @namespaces 40 | # @coverage ||= :all 41 | #else 42 | # @coverage ||= :uncovered 43 | #end 44 | 45 | #@snapshot = Snapshot.capture 46 | 47 | @analyzer = CoverageAnalyzer.new(files + tests, options) 48 | @suite = @analyzer.suite 49 | end 50 | 51 | # 52 | attr :files 53 | 54 | # 55 | attr :tests 56 | 57 | # Group tests by `:case` or `:file`. 58 | attr :group 59 | 60 | # List of class and module namespaces to limit scaffolding. 61 | attr :namespaces 62 | 63 | # Target coverage `:all`, `:uncovered` or `:covered`. 64 | #def coverage 65 | # @coverage 66 | #end 67 | 68 | # Include private and protected methods. 69 | def private? 70 | @private 71 | end 72 | 73 | # Returns CoverageAnalyzer instance. 74 | def analyzer 75 | @analyzer 76 | end 77 | 78 | # Units in groups, by file or by case. 79 | def grouped_units 80 | case group 81 | when :case 82 | units_by_case 83 | when :file 84 | units_by_file 85 | else 86 | units_by_case # default ? 87 | end 88 | end 89 | 90 | # Generate test template(s). 91 | def generate 92 | render_map = {} 93 | 94 | if tests.empty? 95 | grouped_units.each do |group, units| 96 | units = filter(units) 97 | render_map[group] = render(units) 98 | end 99 | else 100 | uncovered_units = analyzer.uncovered 101 | grouped_units.each do |group, units| 102 | units = filter(units) 103 | units = units & uncovered_units 104 | render_map[group] = render(units) 105 | end 106 | #when :covered 107 | # covered_units = analyzer.target.units 108 | # grouped_units.each do |group, units| 109 | # units = filter(units) 110 | # units = units & covered_units 111 | # map[group] = render(units) 112 | # end 113 | #else 114 | # #units = Snapshot.capture(namespaces).units 115 | # #units = (units - @snapshot.units) 116 | end 117 | 118 | render_map 119 | end 120 | 121 | # TODO: If units knew which file they came from that would make 122 | # the code more efficient b/c then we could group after all 123 | # filtering instead of before. 124 | 125 | # 126 | def units_by_case 127 | units = [] 128 | files.each do |file| 129 | units.concat SourceParser.parse_units(File.read(file)) 130 | end 131 | units.group_by{ |u| u.namespace } 132 | end 133 | 134 | # 135 | def units_by_file 136 | map = {} 137 | files.each do |file| 138 | units = SourceParser.parse_units(File.read(file)) 139 | map[file] = units 140 | end 141 | map 142 | end 143 | 144 | # Filter targets to include only specified namespaces. 145 | def filter(units) 146 | return units if namespaces.nil? or namespaces.empty? 147 | units.select do |u| 148 | namespaces.any? do |ns| 149 | /^#{ns}/ =~ u.namespace.to_s 150 | end 151 | end 152 | end 153 | 154 | # Generate code template. 155 | def render(units) 156 | code = [] 157 | mods = units.group_by{ |u| u.namespace } 158 | mods.each do |mod, units| 159 | code << "#{term_case} #{mod} do" 160 | units.each do |unit| 161 | next unless private? or unit.public? 162 | if unit.singleton? 163 | code << "\n #{term_class_method} :#{unit.method} do" 164 | code << "\n test '' do" 165 | code << "\n end" 166 | code << "\n end" 167 | else 168 | code << "\n #{term_method} :#{unit.method} do" 169 | code << "\n test '' do" 170 | code << "\n end" 171 | code << "\n end" 172 | end 173 | end 174 | code << "\nend\n" 175 | end 176 | code.join("\n") 177 | end 178 | 179 | # 180 | def term_case 181 | @caps ? 'TestCase' : 'test_case' 182 | end 183 | 184 | # 185 | def term_class_method 186 | @caps ? 'ClassMethod' : 'class_method' 187 | end 188 | 189 | # 190 | def term_method 191 | @caps ? 'Method' : 'method' 192 | end 193 | 194 | end 195 | 196 | end 197 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.9.1 / 2012-03-17 4 | 5 | Thie release fixes some evaluation scope issues, improves how omit and skip 6 | methods work, makes test generation usable and generally cleans-up internals. 7 | All in all, this release should be a fair bit more robust than previous releases 8 | and ready for the big 1.0 after one more good coding session. 9 | 10 | Changes: 11 | 12 | * Fix test scope so it's properly isolated. 13 | * Improve skip and omit methods w/ non-block forms. 14 | * Use Ripper instead of RubyParse for scaffolding. 15 | * Greatly improve usability of code generation tool. 16 | 17 | 18 | ## 0.9.0 / 2011-08-11 19 | 20 | Version 0.9 is a huge release for Lemon. This release finally makes the transition 21 | to the new unit subcase syntax --a bit more verbose than the old style but a more 22 | flexible means of notating unit tests allowing each unit to group together mutiple 23 | tests. For previous users you will need to change old style tests, e.g. 24 | 25 | Unit :to_s => "that string" do 26 | ... 27 | end 28 | 29 | To the new style: 30 | 31 | Unit :to_s do 32 | Test "that string" do 33 | ... 34 | end 35 | end 36 | 37 | This release also moves Lemon over to the new Ruby Test library for test runs. 38 | The `lemon` command line interface has changed and now uses subcommands rather than command 39 | opitions. So use `lemon coverage` instead of the old `lemon -c`. Since Lemon now uses Ruby 40 | Test to run tests, `lemon test` is equivalent to `ruby-test`, and the Ruby Test config file 41 | should be used to configure test runs instead of `.lemonrc` which is no longer supported. 42 | 43 | Changes: 44 | 45 | * Utilize Ruby Test for test execution. 46 | * Implemented new unit block syntax. 47 | 48 | 49 | ## 0.8.5 / 2011-07-15 50 | 51 | This release fixes exit code status, which should be -1 if there 52 | are errors or failures. 53 | 54 | Changes: 55 | 56 | * Fix exit code status. 57 | 58 | 59 | ## 0.8.4 / 2011-07-11 60 | 61 | Fix reported issue #6 to get lemon passing it own tests. (Note: the 62 | tests need to be run with `test/fixtures` in the loadpath). There was 63 | also a typo in the Outline reporter which was fixed. And better support 64 | of the newest ANSI gem now lets color be deactivated with the --no-ansi 65 | command line option. 66 | 67 | Changes: 68 | 69 | * Fix typo in outline reporter. 70 | * Add --no-ansi command line option. 71 | * Load ansi/core per the latest ANSI gem. 72 | * Lemon can test itself (albeit test are not complete). 73 | 74 | 75 | ## 0.8.3 / 2011-05-19 76 | 77 | Looks like Lemon is pretty damn near 1.0 status. She probably won't get 78 | any major changes for a good while. This release simply adds TAP-Y/J reporters. 79 | Just use `-f tapy` or `-f tapj` on the command line. 80 | 81 | Changes: 82 | 83 | * Add TAP-Y/J reporters. 84 | * Configuration directory can be `.lemon/`. 85 | 86 | 87 | ## 0.8.2 / 2010-09-05 88 | 89 | This release overhauls how coverage is performed so it does not need to 90 | take a system snapshot after requiring each covered file. This greatly 91 | improves Lemon's speed. In addition #setup and #teardown have been introduced 92 | for performing procedures before and after each unit test. 93 | 94 | Changes: 95 | 96 | * Overhaul coverage analysis. 97 | * Add TestCase#setup and #teardown methods. 98 | * TestCase#concern and #context are just aliases of #setup. 99 | * Improved output formats. 100 | 101 | 102 | ## 0.8.1 / 2010-07-11 103 | 104 | This release adds a timer to the verbose output, which help isolate unit tests 105 | that are slow. It also fixed bug in Before and After advice that prevented them 106 | from triggering correctly. 107 | 108 | Changes: 109 | 110 | * Add times to verbose reporter. 111 | 112 | 113 | ## 0.8.0 / 2010-06-21 114 | 115 | This release removes coverage information from testing. Coverage can be time 116 | consuming, but running test should be as fast as possbile. For this reason 117 | coverage and testing are kept two independent activities. This release also 118 | adds some test coverage for Lemon itself via Cucumber. 119 | 120 | Changes: 121 | 122 | * Separated coverage from testing completely. 123 | * Test generator defaults to public methods only. 124 | 125 | 126 | ## 0.7.0 / 2010-05-04 127 | 128 | This release fixes issue with coverage reports. To do this we have interoduced 129 | the +Covers+ method. This allows Lemon to distingush between code that is 130 | inteded to be covered by the tests and mere support code. 131 | 132 | Keep in mind that there is no perfect way to handle coverage. Even with the 133 | distiction the +Covers+ method provides, coverage might not be reported exactly 134 | as desired. Other techinques can be used to refine coverage however, such 135 | a preloading embedded support libraries. 136 | 137 | Changes: 138 | 139 | * Add +Covers+ method to solidify coverage reporting. 140 | * New Snapshot class improves encapsulation to coverage state. 141 | 142 | 143 | ## 0.6.0 / 2010-03-06 144 | 145 | This release adds coverage reporting to testing and improves the generator. 146 | 147 | Changes: 148 | 149 | * Runner can provide uncovered and undefined testunit list. 150 | * Generator can exclude already covered testunits with -u option. 151 | * Suite class has Coverage instance. 152 | 153 | 154 | ## 0.5.0 / 2009-12-31 155 | 156 | This is the initial public release of Lemon. Lemon is still under development 157 | and should be considered betaware, but it's API is stable and the system usable 158 | enough to warrant a release. 159 | 160 | Changes: 161 | 162 | * Happy First Release Day! 163 | 164 | -------------------------------------------------------------------------------- /pkg/lemon.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # 11 | DOTRUBY = '{../,}.ruby' unless defined?(DOTRUBY) 12 | 13 | # 14 | MANIFEST = '{../,}manifest{,.txt}' unless defined?(MANIFEST) 15 | 16 | # For which revision of .ruby is this gemspec intended? 17 | REVISION = 0 unless defined?(REVISION) 18 | 19 | # 20 | PATTERNS = { 21 | :bin_files => 'bin/*', 22 | :lib_files => 'lib/{**/}*.rb', 23 | :ext_files => 'ext/{**/}extconf.rb', 24 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 25 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 26 | } unless defined?(PATTERNS) 27 | 28 | # 29 | def self.instance 30 | new.to_gemspec 31 | end 32 | 33 | attr :metadata 34 | 35 | attr :manifest 36 | 37 | # 38 | def initialize 39 | @metadata = YAML.load_file(Dir.glob(DOTRUBY).first) 40 | @manifest = Dir.glob(MANIFEST, File::FNM_CASEFOLD).first 41 | 42 | if @metadata['revision'].to_i != REVISION 43 | warn "You have the wrong revision. Trying anyway..." 44 | end 45 | end 46 | 47 | # 48 | def scm 49 | @scm ||= \ 50 | case 51 | when File.directory?('.git') 52 | :git 53 | end 54 | end 55 | 56 | # 57 | def files 58 | @files ||= \ 59 | #glob_files[patterns[:files]] 60 | case 61 | when manifest 62 | File.readlines(manifest). 63 | map{ |line| line.strip }. 64 | reject{ |line| line.empty? || line[0,1] == '#' } 65 | when scm == :git 66 | `git ls-files -z`.split("\0") 67 | else 68 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 69 | end.select{ |path| File.file?(path) } 70 | end 71 | 72 | # 73 | def glob_files(pattern) 74 | Dir.glob(pattern).select { |path| 75 | File.file?(path) && files.include?(path) 76 | } 77 | end 78 | 79 | # 80 | def patterns 81 | PATTERNS 82 | end 83 | 84 | # 85 | def executables 86 | @executables ||= \ 87 | glob_files(patterns[:bin_files]).map do |path| 88 | File.basename(path) 89 | end 90 | end 91 | 92 | def extensions 93 | @extensions ||= \ 94 | glob_files(patterns[:ext_files]).map do |path| 95 | File.basename(path) 96 | end 97 | end 98 | 99 | # 100 | def name 101 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 102 | end 103 | 104 | # 105 | def to_gemspec 106 | Gem::Specification.new do |gemspec| 107 | gemspec.name = name 108 | gemspec.version = metadata['version'] 109 | gemspec.summary = metadata['summary'] 110 | gemspec.description = metadata['description'] 111 | 112 | metadata['authors'].each do |author| 113 | gemspec.authors << author['name'] 114 | 115 | if author.has_key?('email') 116 | if gemspec.email 117 | gemspec.email << author['email'] 118 | else 119 | gemspec.email = [author['email']] 120 | end 121 | end 122 | end 123 | 124 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 125 | 126 | metadata['requirements'].each do |req| 127 | next if req['optional'] 128 | 129 | name = req['name'] 130 | version = req['version'] 131 | groups = req['groups'] || [] 132 | 133 | case version 134 | when /^(.*?)\+$/ 135 | version = ">= #{$1}" 136 | when /^(.*?)\-$/ 137 | version = "< #{$1}" 138 | when /^(.*?)\~$/ 139 | version = "~> #{$1}" 140 | end 141 | 142 | if groups.empty? or groups.include?('runtime') 143 | # populate runtime dependencies 144 | if gemspec.respond_to?(:add_runtime_dependency) 145 | gemspec.add_runtime_dependency(name,*version) 146 | else 147 | gemspec.add_dependency(name,*version) 148 | end 149 | else 150 | # populate development dependencies 151 | if gemspec.respond_to?(:add_development_dependency) 152 | gemspec.add_development_dependency(name,*version) 153 | else 154 | gemspec.add_dependency(name,*version) 155 | end 156 | end 157 | end 158 | 159 | # convert external dependencies into a requirements 160 | if metadata['external_dependencies'] 161 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 162 | metadata['external_dependencies'].each do |req| 163 | gemspec.requirements << req.to_s 164 | end 165 | end 166 | 167 | # determine homepage from resources 168 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 169 | gemspec.homepage = homepage.last if homepage 170 | 171 | gemspec.require_paths = metadata['load_path'] || ['lib'] 172 | gemspec.post_install_message = metadata['install_message'] 173 | 174 | # RubyGems specific metadata 175 | gemspec.files = files 176 | gemspec.extensions = extensions 177 | gemspec.executables = executables 178 | 179 | if Gem::VERSION < '1.7.' 180 | gemspec.default_executable = gemspec.executables.first 181 | end 182 | 183 | gemspec.test_files = glob_files(patterns[:test_files]) 184 | 185 | unless gemspec.files.include?('.document') 186 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 187 | end 188 | end 189 | end 190 | 191 | end #class GemSpec 192 | 193 | end 194 | 195 | DotRuby::GemSpec.instance 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lemon 2 | 3 | [Homepage](http://rubyworks.github.com/lemon) | 4 | [User Guide](http://wiki.github.com/rubyworks/lemon) | 5 | [Development](http://github.com/rubyworks/lemon) | 6 | [Issues](http://github.com/rubyworks/lemon/issues) 7 | 8 | [![Build Status](https://secure.travis-ci.org/rubyworks/lemon.png)](http://travis-ci.org/rubyworks/lemon) 9 | 10 | 11 | ## DESCRIPTION 12 | 13 | Lemon is a Unit Testing Framework that enforces a strict test structure mirroring the class/module and method structure of the target code. Arguably this promotes the proper technique for low-level unit testing and helps ensure good test coverage. 14 | 15 | The difference between unit testing and functional testing, and all other forms of testing for that matter, is simply a matter of where the testing *concern* lies. The concerns of unit testing are the concerns of unit tests, which, in object-oriented design, drills down to individual methods. 16 | 17 | **IMPORTANT!** As of v0.9+ the API has changed. The `unit :name => "description"` notation is no longer supported. 18 | 19 | 20 | ## EXAMPLE 21 | 22 | Lemon tests are broken down into target class or module and target methods. 23 | Within these lie the actual tests. 24 | 25 | Let's say we have a script 'mylib.rb' consisting of the class X: 26 | 27 | ``` ruby 28 | class X 29 | def a; "a"; end 30 | end 31 | ``` 32 | 33 | An test case for the class would be written: 34 | 35 | ``` ruby 36 | covers 'mylib' 37 | 38 | testcase X do 39 | 40 | setup "Description of setup." do 41 | @x = X.new 42 | end 43 | 44 | method :a do 45 | test "method #a does something expected" do 46 | @x.a.assert.is_a? String 47 | end 48 | 49 | test "method #a does something else expected" do 50 | @x.a.assert == "x" 51 | end 52 | end 53 | 54 | end 55 | ``` 56 | 57 | The `covers` method works just like `require` with the exception that it records the file for reference --under certain scenarios it can be used to improve test coverage analysis. 58 | 59 | The setup (also called the *concern*) is run for every subsequent test until a new setup is defined. 60 | 61 | In conjunction with the `#setup` method, there is a `#teardown` method which can be used "tidy-up" after each test. 62 | 63 | The `#unit` method is also aliased as `#methed` which is actually a bit more readable. Along with that there is `class_unit` and it's alias `class_method` for testing class-level methods. Also note that the test methods may be capitalized (e.g. `#TestCase'), if you prefer that style. 64 | 65 | That is the bulk of the matter for writing Lemon tests. To learn about additional features not mentioned here, check-out the [User Guide](http://wiki.github.com/rubyworks/lemon). 66 | 67 | 68 | ## USAGE 69 | 70 | ### Running Tests 71 | 72 | Tests can be run using the `lemons` command line tool. 73 | 74 | $ lemons test test/cases/name_case.rb 75 | 76 | Lemon utilizes the RubyTest universal test harness to run tests, the `lemons test` command simply passes off control to `rubytest` command. So the `rubytest` command-line utility can also be used directly: 77 | 78 | $ rubytest -r lemon test/cases/name_case.rb 79 | 80 | Normal output is typically a _dot progression_. Other output types can be specified by the `--format` or `-f` option. 81 | 82 | $ rubytest -r lemon -f tapy test/cases/name_case.rb 83 | 84 | See [RubyTest](http://rubyworks.github.com/rubytest) for more information. 85 | 86 | ### Checking Test Coverage 87 | 88 | Lemon can check per-unit test coverage by loading your target system and comparing it to your tests. To do this use the `lemons coverage` command. 89 | 90 | $ lemons coverage -Ilib test/cases/*.rb 91 | 92 | The coverage tool provides class/module and method coverage and is meant as a "guidance system" for developers working toward complete test coverage. It is not a LOC (lines of code) coverage tool and should not be considered a substitute for one. 93 | 94 | ### Generating Test Skeletons 95 | 96 | Because of the one-to-one correspondence of test case and unit test to class/module and method, Lemon can also generate test scaffolding for previously written code. To do this, use the `lemons generate` or `lemons scaffold` command line utilities. 97 | 98 | The `generate` command outputs test skeletons to the console. You can use this output as a simple reference or redirect the output to a file and then copy and paste portions into separate files as desired. The `scaffold` command will create actual files in your test directory. Other than that, and the options that go with it (e.g. `--output`), the two commands are the same. 99 | 100 | To get a set of test skeletons simply provide the files to be covered. 101 | 102 | $ lemons generate lib/**/*.rb 103 | 104 | The generator can take into account tests already written, so as not to include units that already have tests. To do this provide a list of test files after a dash (`-`). 105 | 106 | $ lemons generate -Ilib lib/**/*.rb - test/**/*.rb 107 | 108 | Test skeletons can be generated on a per-file or per-case bases. By case is the default. Use the `-f`/`--file` option to do otherwise. 109 | 110 | $ lemon scaffold -f lib/foo.rb 111 | 112 | The default output location is `test/`. You can change this with the `-o/--output` option. 113 | 114 | Generating test case scaffolding from code will undoubtedly strike test-driven developers as a case of putting the cart before the horse. However, it is not unreasonable to argue that high-level, behavior-driven, functional testing frameworks, such as Q.E.D. and Cucumber, are better suited to test-first methodologies. While test-driven development can obviously be done with Lemon, unit-testing best suited to testing specific, critical portions of code, or for achieving full test coverage for mission critical applications. 115 | 116 | ### Test Directory 117 | 118 | There is no special directory for Lemon tests. Since they are unit tests, `test/` or `test/unit/` are good choices. Other options are `cases/` and `test/cases` since each file generally defines a single test case. 119 | 120 | 121 | ## COPYRIGHTS 122 | 123 | Lemon Unit Testing Framework 124 | 125 | Copyright (c) 2009 Thomas Sawyer, Rubyworks 126 | 127 | Lemon is distributable in accordance with the **FreeBSD** license. 128 | 129 | See the LICENSE.txt for details. 130 | -------------------------------------------------------------------------------- /lib/lemon/cli/lemon.ascii: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /lib/lemon/coverage/source_parser.rb: -------------------------------------------------------------------------------- 1 | require 'ripper' 2 | require 'lemon/coverage/snapshot' 3 | 4 | module Lemon 5 | 6 | # 7 | class SourceParser 8 | # Converts Ruby code into a data structure. 9 | # 10 | # text - A String of Ruby code. 11 | # 12 | # Returns a Hash with each key a namespace and each value another Hash or Scope. 13 | def self.parse(text) 14 | new.parse(text) 15 | end 16 | 17 | # Converts Ruby code into a data structure. 18 | # 19 | # text - A String of Ruby code. 20 | # 21 | # Returns an Array of Snapshot::Unit objects. 22 | def self.parse_units(text) 23 | sp = new 24 | sp.parse(text) 25 | sp.units 26 | end 27 | 28 | attr_accessor :parser 29 | 30 | attr_accessor :scopes 31 | 32 | attr_accessor :options 33 | 34 | # Each instance of SourceParser accumulates scopes with each 35 | # parse, making it easy to parse an entire project in chunks but 36 | # more difficult to parse disparate files in one go. Create 37 | # separate instances for separate global scopes. 38 | # 39 | # Returns an instance of SourceParser. 40 | def initialize(options = {}) 41 | @options = {} 42 | @scopes = {} 43 | #@parser = RubyParser.new 44 | end 45 | 46 | # Resets the state of the parser to a pristine one. Maintains options. 47 | # 48 | # Returns nothing. 49 | def reset 50 | initialize(@options) 51 | end 52 | 53 | # Converts Ruby code into a data structure. Note that at the 54 | # instance level scopes accumulate, which makes it easy to parse 55 | # multiple files in a single project but harder to parse files 56 | # that have no connection. 57 | # 58 | # text - A String of Ruby code. 59 | # 60 | # Examples 61 | # @parser = SourceParser.new 62 | # files.each do |file| 63 | # @parser.parse(File.read(file)) 64 | # end 65 | # pp @parser.scopes 66 | # 67 | # Returns a Hash with each key a namespace and each value another Hash or Scope. 68 | def parse(text) 69 | process(tokenize(sexp(text))) 70 | @scopes 71 | end 72 | 73 | # Converts Ruby sourcecode into an AST. 74 | # 75 | # text - A String of Ruby source. 76 | # 77 | # Returns a Sexp representing the AST. 78 | def sexp(text) 79 | Ripper.sexp(text) 80 | #@parser.parse(text) 81 | end 82 | 83 | # Converts a tokenized Array of classes, modules, and methods into 84 | # Scopes and Methods, adding them to the @scopes instance variable 85 | # as it works. 86 | # 87 | # ast - Tokenized Array produced by calling `tokenize`. 88 | # scope - An optional Scope object for nested classes or modules. 89 | # 90 | # Returns nothing. 91 | def process(ast, scope=nil) 92 | case Array(ast)[0] 93 | when :module, :class 94 | name = ast[1] 95 | new_scope = Scope.new(name, ast[2]) 96 | 97 | if scope 98 | new_scope.parent = scope 99 | scope.scopes[name] = new_scope 100 | elsif @scopes[name] 101 | new_scope = @scopes[name] 102 | else 103 | @scopes[name] = new_scope 104 | end 105 | 106 | process(ast[3], new_scope) 107 | when :imethod 108 | ast.shift 109 | scope.instance_methods << Method.new(*ast) 110 | when :cmethod 111 | ast.shift 112 | scope.class_methods << Method.new(*ast) 113 | when Array 114 | ast.map { |a| process(a, scope) } 115 | end 116 | end 117 | 118 | # Converts a Ruby AST-style Sexp into an Array of more useful tokens. 119 | # 120 | # node - A Ruby AST Sexp or Array 121 | # 122 | # Examples 123 | # 124 | # [:module, :Math, "", 125 | # [:class, :Multiplexer, "# Class Comment", 126 | # [:cmethod, 127 | # :multiplex, "# Class Method Comment", [:text]], 128 | # [:imethod, 129 | # :multiplex, "# Instance Method Comment", [:text, :count]]]] 130 | # 131 | # # In others words: 132 | # # [ :type, :name, :comment, other ] 133 | # 134 | # Returns an Array in the above format. 135 | def tokenize(node) 136 | case Array(node)[0] 137 | when :module 138 | name = node[1][1][1] 139 | [ :module, name, '', tokenize(node[2]) ] 140 | when :class 141 | name = node[1][1][1] 142 | [ :class, name, '', tokenize(node[3]) ] 143 | when :def 144 | name = node[1][1] 145 | args = args_for_node(node[2]) 146 | [ :imethod, name, '', args ] 147 | when :defs 148 | name = node[3][1] 149 | args = args_for_node(node[4]) 150 | [ :cmethod, name, '', args ] 151 | when :block 152 | tokenize(node[1..-1]) 153 | when :program, :bodystmt, :scope 154 | tokenize(node[1]) 155 | when Array 156 | node.map { |n| tokenize(n) }.compact 157 | end 158 | end 159 | 160 | # Given a method sexp, returns an array of the args. 161 | def args_for_node(node) 162 | Array(node)[1..-1].select{ |arg| arg.is_a? Symbol } 163 | end 164 | 165 | # 166 | def units 167 | list = [] 168 | @scopes.each do |name, scope| 169 | list.concat(scope.to_units) 170 | end 171 | list 172 | end 173 | 174 | # A Scope is a Module or Class, and may contain other scopes. 175 | class Scope 176 | include Enumerable 177 | 178 | attr_accessor :name, :comment, :instance_methods, :class_methods 179 | 180 | attr_accessor :parent 181 | 182 | attr_accessor :scopes 183 | 184 | def initialize(name, comment='', instance_methods=[], class_methods=[]) 185 | @name = name 186 | @comment = comment 187 | @instance_methods = instance_methods 188 | @class_methods = class_methods 189 | @scopes = {} 190 | end 191 | 192 | def [](scope) 193 | @scopes[scope] 194 | end 195 | 196 | def keys 197 | @scopes.keys 198 | end 199 | 200 | def each(&block) 201 | @scopes.each(&block) 202 | end 203 | 204 | def to_s 205 | inspect 206 | end 207 | 208 | def inspect 209 | scopes = @scopes.keys.join(', ') 210 | imethods = @instance_methods.inspect 211 | cmethods = @class_methods.inspect 212 | 213 | "<#{name} scopes:[#{scopes}] :#{cmethods}: ##{imethods}#>" 214 | end 215 | 216 | # 217 | def target 218 | if parent 219 | parent.target.const_get(name) 220 | else 221 | Object.const_get(name) 222 | end 223 | end 224 | 225 | # 226 | def to_units 227 | units = [] 228 | @instance_methods.each do |imethod| 229 | units << Snapshot::Unit.new(target, imethod, :singleton=>false) 230 | end 231 | @class_methods.each do |imethod| 232 | units << Snapshot::Unit.new(target, imethod, :singleton=>true) 233 | end 234 | @scopes.each do |name, scope| 235 | units.concat(scope.to_units) 236 | end 237 | units 238 | end 239 | 240 | end 241 | 242 | # A Method can be instance or class level. 243 | class Method 244 | attr_accessor :name, :comment, :args 245 | 246 | def initialize(name, comment='', args=[]) 247 | @name = name 248 | @comment = comment 249 | @args = args || [] 250 | end 251 | alias_method :to_s, :name 252 | 253 | def to_sym 254 | name.to_sym 255 | end 256 | 257 | def inspect 258 | "#{name}(#{args.join(', ')})" 259 | end 260 | end 261 | 262 | end 263 | 264 | end 265 | -------------------------------------------------------------------------------- /work/deprecated/model/test_suite.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/model/test_case' 2 | require 'lemon/model/test_method' 3 | require 'lemon/model/test_module' 4 | require 'lemon/model/test_feature' 5 | require 'lemon/model/snapshot' 6 | #require 'lemon/model/main' 7 | require 'lemon/core_ext/kernel' 8 | 9 | module Lemon 10 | 11 | # Current suite being defined. This is used 12 | # to define a Suite object via the toplevel DSL. 13 | def self.suite 14 | $lemon_suite #@suite ||= Lemon::TestSuite.new([]) 15 | end 16 | 17 | # 18 | def self.suite=(suite) 19 | $lemon_suite = suite 20 | end 21 | 22 | # Test Suites encapsulate a set of test cases. 23 | # 24 | class TestSuite 25 | 26 | # Files from which the suite is loaded. 27 | attr :files 28 | 29 | # Test cases in this suite. 30 | attr :cases 31 | 32 | # List of pre-test procedures that apply suite-wide. 33 | attr :before 34 | 35 | # List of post-test procedures that apply suite-wide. 36 | attr :after 37 | 38 | # A snapshot of the system before the suite is loaded. 39 | # Only set if +cover+ option is true. 40 | #attr :canonical 41 | 42 | # List of files to be covered. This primarily serves 43 | # as a means for allowing one test to load another 44 | # and ensuring converage remains accurate. 45 | #attr :subtest 46 | 47 | #attr :current_file 48 | 49 | #def coverage 50 | # @final_coveage ||= @coverage - @canonical 51 | #end 52 | 53 | # 54 | attr :options 55 | 56 | attr :stack 57 | 58 | attr :dsl 59 | 60 | # 61 | def initialize(files, options={}) 62 | @files = files.flatten 63 | @options = options 64 | 65 | @cases = [] 66 | @helpers = [] 67 | 68 | @before = {} 69 | @after = {} 70 | 71 | load_helpers 72 | 73 | #if cover? or cover_all? 74 | # @coverage = Snapshot.new 75 | # @canonical = Snapshot.capture 76 | #end 77 | 78 | @dsl = DSL.new(self) #, files) 79 | 80 | load_files 81 | end 82 | 83 | # 84 | def cover? 85 | @options[:cover] 86 | end 87 | 88 | # 89 | def cover_all? 90 | @options[:cover_all] 91 | end 92 | 93 | # 94 | #class Scope < Module 95 | # def initialize 96 | # extend self 97 | # end 98 | #end 99 | 100 | def to_a 101 | @cases 102 | end 103 | 104 | # Iterate through this suite's test cases. 105 | def each(&block) 106 | @cases.each(&block) 107 | end 108 | 109 | # 110 | def advice 111 | @advice ||= TestAdvice.new 112 | end 113 | 114 | # 115 | def subject 116 | @subject 117 | end 118 | 119 | =begin 120 | # 121 | def start_suite 122 | end 123 | 124 | # 125 | def finish_suite 126 | end 127 | =end 128 | 129 | # 130 | def scope 131 | s = Object.new 132 | s.extend(dsl) 133 | s 134 | end 135 | 136 | # Automatically load helpers. Helpers are any *.rb script in 137 | # a `helpers` directory, relative to a test script. 138 | # 139 | # TODO: You can change the file pattern used to automatically 140 | # load helper scripts in `.lemon`. 141 | # 142 | def load_helpers 143 | helpers = [] 144 | filelist.each do |file| 145 | dir = File.dirname(file) 146 | hlp = Dir[File.join(dir, 'helper{s,}/*.rb')] 147 | helpers.concat(hlp) 148 | end 149 | helpers.uniq! 150 | helpers.each do |hlp| 151 | require File.expand_path(hlp) 152 | end 153 | @helpers = helpers 154 | end 155 | 156 | # 157 | def load_files #(*files) 158 | s = Lemon.suite || self 159 | Lemon.suite = self 160 | 161 | filelist.each do |file| 162 | #load_file(file) 163 | load file #require file 164 | end 165 | 166 | Lemon.suite = s 167 | 168 | #if cover? 169 | # $stdout << "\n" 170 | # $stdout.flush 171 | #end 172 | 173 | self #return Lemon.suite 174 | end 175 | 176 | # 177 | #def load_file(file) 178 | # #@current_file = file 179 | # #if cover_all? 180 | # # Covers(file) 181 | # #else 182 | # file = File.expand_path(file) 183 | # @dsl.module_eval(File.read(file), file) 184 | # #require(file) #load(file) 185 | # #end 186 | #end 187 | 188 | # Directories glob *.rb files. 189 | def filelist 190 | @filelist ||= ( 191 | files = @files 192 | files = files.map{ |f| Dir[f] }.flatten 193 | files = files.map do |file| 194 | if File.directory?(file) 195 | Dir[File.join(file, '**', '*.rb')] 196 | else 197 | file 198 | end 199 | end.flatten 200 | #files = files.map{ |f| File.expand_path(f) } 201 | files.uniq 202 | files.reject{ |f| /fixture(|s)\/(.*?)\.rb$/ =~ f } 203 | files.reject{ |f| /helper(|s)\/(.*?)\.rb$/ =~ f } 204 | ) 205 | end 206 | 207 | # TODO: Note sure about scope creation here 208 | def scope 209 | @scope ||= ( 210 | scope = Object.new 211 | scope.extend(dsl) 212 | ) 213 | end 214 | 215 | class DSL < Module 216 | # 217 | def initialize(suite) 218 | @suite = suite 219 | #module_eval(&code) 220 | end 221 | 222 | # TODO: need require_find() to avoid first snapshot ? 223 | def covers(file) 224 | #if @test_suite.cover? 225 | # #return if $".include?(file) 226 | # s = Snapshot.capture 227 | # if require(file) 228 | # z = Snapshot.capture 229 | # @test_suite.coverage << (z - s) 230 | # end 231 | #else 232 | require file 233 | #end 234 | end 235 | alias_method :Covers, :covers 236 | 237 | # Define a test case belonging to this suite. 238 | def test_case(description, &block) 239 | options = { 240 | :description => description 241 | } 242 | @suite.cases << TestCase.new(@suite, options, &block) 243 | end 244 | 245 | # 246 | alias_method :TestCase, :test_case 247 | 248 | # Define a module test case belonging to this suite. 249 | def test_module(target_module, &block) 250 | raise "lemon: target must be a module" unless Module === target_module 251 | options = { 252 | :target => target_module 253 | } 254 | @suite.cases << TestModule.new(@suite, optios, &block) 255 | end 256 | 257 | # Define a class test case belonging to this suite. 258 | def test_class(target_class, &block) 259 | raise "lemon: case target must be a class" unless Class === target_class 260 | options = { 261 | :target => target_class 262 | } 263 | @suite.cases << TestModule.new(@suite, options, &block) 264 | end 265 | 266 | # Define a test feature. 267 | def test_feature(target, &block) 268 | options = { 269 | :target => target 270 | } 271 | @suite.cases << TestFeature.new(@suite, options, &block) 272 | end 273 | 274 | # Define a pre-test procedure to apply suite-wide. 275 | def before(*matches, &block) 276 | @suite.before[matches] = block #<< Advice.new(match, &block) 277 | end 278 | alias_method :Before, :before 279 | 280 | # Define a post-test procedure to apply suite-wide. 281 | def after(*matches, &block) 282 | @suite.after[matches] = block #<< Advice.new(match, &block) 283 | end 284 | alias_method :After, :after 285 | 286 | # Includes at the suite level are routed to the toplevel. 287 | #def include(*mods) 288 | # TOPLEVEL_BINDING.eval('self').instance_eval do 289 | # include(*mods) 290 | # end 291 | #end 292 | 293 | end 294 | 295 | end 296 | 297 | end 298 | -------------------------------------------------------------------------------- /lib/lemon/test_case.rb: -------------------------------------------------------------------------------- 1 | require 'lemon/core_ext' 2 | require 'lemon/test_advice' 3 | require 'lemon/test_setup' 4 | require 'lemon/test_world' 5 | require 'lemon/test_scope' 6 | 7 | module Lemon 8 | 9 | # Test Case serves as the base class for Lemon's 10 | # specialized test case classes. 11 | # 12 | class TestCase 13 | 14 | # The parent context in which this case resides. 15 | attr :context 16 | 17 | # Brief description of the test case. 18 | attr :label 19 | 20 | # Target component. 21 | attr :target 22 | 23 | # The setup and teardown advice. 24 | attr :setup 25 | 26 | # Advice are labeled procedures, such as before 27 | # and after advice. 28 | attr :advice 29 | 30 | # List of tests and sub-contexts. 31 | attr :tests 32 | 33 | # Skip execution of test case? 34 | attr :skip 35 | 36 | # 37 | # A test case +target+ is a class or module. 38 | # 39 | # @param [Hash] settings 40 | # The settings used to define the test case. 41 | # 42 | # @option settings [TestCase] :context 43 | # Parent test case. 44 | # 45 | # @option settings [Module,Class,Symbol] :target 46 | # The testcase's target. 47 | # 48 | # @option settings [String] :label 49 | # Breif description of testcase. 50 | # (NOTE: this might not be used) 51 | # 52 | # @option settings [TestSetup] :setup 53 | # Test setup. 54 | # 55 | # @option settings [Boolean] :skip 56 | # If runner should skip test. 57 | # 58 | def initialize(settings={}, &block) 59 | @context = settings[:context] 60 | @target = settings[:target] 61 | @label = settings[:label] 62 | @setup = settings[:setup] 63 | @skip = settings[:skip] 64 | @tags = settings[:tags] 65 | 66 | @advice = @context ? @context.advice.dup : TestAdvice.new 67 | 68 | @tests = [] 69 | @domain = domain_class.new(self) 70 | 71 | validate_settings 72 | 73 | evaluate(&block) 74 | end 75 | 76 | # 77 | # Subclasses can override this methof to validate settings. 78 | # It is run just before evaluation of scope block. 79 | # 80 | def validate_settings 81 | end 82 | 83 | # 84 | def domain 85 | @domain 86 | end 87 | 88 | # 89 | # 90 | # 91 | def evaluate(&block) 92 | @domain.module_eval(&block) 93 | end 94 | 95 | # 96 | # Iterate over each test and subcase. 97 | # 98 | def each(&block) 99 | tests.each(&block) 100 | end 101 | 102 | # 103 | # Number of tests and subcases. 104 | # 105 | def size 106 | tests.size 107 | end 108 | 109 | # 110 | # Subclasses of TestCase can override this to describe 111 | # the type of test case they define. 112 | # 113 | def type 114 | 'Test Case' 115 | end 116 | 117 | # 118 | # 119 | # 120 | def to_s 121 | @label.to_s 122 | end 123 | 124 | # 125 | # 126 | # 127 | def skip? 128 | @skip 129 | end 130 | 131 | # 132 | # 133 | # 134 | def skip!(reason=true) 135 | @skip = reason 136 | end 137 | 138 | # 139 | # 140 | # 141 | def tags 142 | @tags 143 | end 144 | 145 | # 146 | # Run test in the context of this case. 147 | # 148 | # @param [TestProc] test 149 | # The test unit to run. 150 | # 151 | def run(test, &block) 152 | advice[:before].each do |matches, block| 153 | if matches.all?{ |match| test.match?(match) } 154 | scope.instance_exec(test, &block) #block.call(unit) 155 | end 156 | end 157 | 158 | block.call 159 | 160 | advice[:after].each do |matches, block| 161 | if matches.all?{ |match| test.match?(match) } 162 | scope.instance_exec(test, &block) #block.call(unit) 163 | end 164 | end 165 | end 166 | 167 | # 168 | # Module for evaluating test case script. 169 | # 170 | # @return [Scope] evaluation scope 171 | # 172 | def scope 173 | @scope ||= TestScope.new(self) 174 | end 175 | 176 | # 177 | # Get the domain class dynamically so that each subclass 178 | # of TestCase will retrieve it's own. 179 | # 180 | def domain_class 181 | self.class.const_get(:DSL) 182 | end 183 | 184 | # 185 | class DSL < World 186 | 187 | # 188 | # The class for which this is a DSL context. 189 | # 190 | def context_class 191 | TestCase 192 | end 193 | 194 | # 195 | # 196 | # 197 | def initialize(testcase) #, &code) 198 | @_testcase = testcase 199 | @_setup = testcase.setup 200 | @_skip = nil 201 | 202 | include testcase.context.domain if testcase.context 203 | 204 | #module_eval(&code) 205 | end 206 | 207 | # 208 | # Setup is used to set things up for each unit test. 209 | # The setup procedure is run before each unit. 210 | # 211 | # @param [String] description 212 | # A brief description of what the setup procedure sets-up. 213 | # 214 | def setup(description=nil, &procedure) 215 | if procedure 216 | @_setup = TestSetup.new(@test_case, description, &procedure) 217 | end 218 | end 219 | alias :Setup :setup 220 | 221 | # 222 | # Original Lemon nomenclature for `#setup`. 223 | # 224 | alias :concern :setup 225 | alias :Concern :setup 226 | 227 | # 228 | # Teardown procedure is used to clean-up after each unit test. 229 | # 230 | def teardown(&procedure) 231 | @_setup.teardown = procedure 232 | end 233 | alias :Teardown :teardown 234 | 235 | # TODO: Allow Before and After to handle setup and teardown? 236 | # But that would only allow one setup per case. 237 | 238 | # 239 | # Define a _complex_ before procedure. The #before method allows 240 | # before procedures to be defined that are triggered by a match 241 | # against the unit's target method name or _aspect_ description. 242 | # This allows groups of tests to be defined that share special 243 | # setup code. 244 | # 245 | # @example 246 | # Method :puts do 247 | # Test "standard output (@stdout)" do 248 | # puts "Hello" 249 | # end 250 | # 251 | # Before /@stdout/ do 252 | # $stdout = StringIO.new 253 | # end 254 | # 255 | # After /@stdout/ do 256 | # $stdout = STDOUT 257 | # end 258 | # end 259 | # 260 | # @param [Array] matches 261 | # List of match critera that must _all_ be matched 262 | # to trigger the before procedure. 263 | # 264 | def before(*matches, &procedure) 265 | @_testcase.advice[:before][matches] = procedure 266 | end 267 | alias :Before :before 268 | 269 | # 270 | # Define a _complex_ after procedure. The #before method allows 271 | # before procedures to be defined that are triggered by a match 272 | # against the unit's target method name or _aspect_ description. 273 | # This allows groups of tests to be defined that share special 274 | # teardown code. 275 | # 276 | # @example 277 | # Method :puts do 278 | # Test "standard output (@stdout)" do 279 | # puts "Hello" 280 | # end 281 | # 282 | # Before /@stdout/ do 283 | # $stdout = StringIO.new 284 | # end 285 | # 286 | # After /@stdout/ do 287 | # $stdout = STDOUT 288 | # end 289 | # end 290 | # 291 | # @param [Array] matches 292 | # List of match critera that must _all_ be matched 293 | # to trigger the after procedure. 294 | # 295 | def after(*matches, &procedure) 296 | @_testcase.advice[:after][matches] = procedure 297 | end 298 | alias :After :after 299 | 300 | # THINK: Instead of resuing TestCase can we have a TestContext 301 | # or other way to more generically mimics the parent context? 302 | 303 | # 304 | # Create a subcase of module testcase. 305 | # 306 | def context(label, *tags, &block) 307 | return if @_omit 308 | 309 | @_testcase.tests << context_class.new( 310 | :context => @_testcase, 311 | :target => @_testcase.target, 312 | :setup => @_setup, 313 | :skip => @_skip, 314 | :label => label, 315 | :tags => tags, 316 | &block 317 | ) 318 | end 319 | alias :Context :context 320 | 321 | # 322 | # Skip tests. Unlike omit, skipped tests are passed to the test harness, 323 | # so they still can be included in reports, though they are not executed. 324 | # 325 | # If a block is given then only tests defined with-in the block are skipped. 326 | # If no block is given then all subsquent tests in the test case are skipped. 327 | # 328 | # @param [String,Boolean] reason 329 | # A description of the reason to skip the test, or simply a boolean value. 330 | # 331 | # @example 332 | # skip "reason" do 333 | # test do 334 | # ... 335 | # end 336 | # end 337 | # 338 | def skip(reason=true) 339 | if block_given? 340 | @_skip = reason 341 | yield 342 | @_skip = nil 343 | else 344 | @_skip = reason 345 | end 346 | end 347 | alias :Skip :skip 348 | 349 | # 350 | # Omitted tests are simply ignored and never instantiated let alone passed 351 | # on to the test harness. 352 | # 353 | # If a block is given then only tests defined with-in the block are skipped. 354 | # If no block is given then all subsquent tests in the test case are skipped. 355 | # 356 | # 357 | # @example 358 | # omit do 359 | # test do 360 | # ... 361 | # end 362 | # end 363 | # 364 | def omit(reason=true) 365 | if block_given? 366 | @_omit = reason 367 | yield 368 | @_omit = nil 369 | else 370 | @_omit = reason 371 | end 372 | end 373 | alias :Omit :omit 374 | 375 | end 376 | 377 | end 378 | 379 | end 380 | -------------------------------------------------------------------------------- /lib/lemon/coverage/analyzer.rb: -------------------------------------------------------------------------------- 1 | require 'lemon' 2 | require 'lemon/coverage/snapshot' 3 | 4 | module Lemon 5 | 6 | # 7 | class CoverageAnalyzer 8 | 9 | ## New Coverage object. 10 | ## 11 | ## Coverage.new('lib/', :MyApp, :public => true) 12 | ## 13 | #def initialize(suite_or_files, namespaces=nil, options={}) 14 | # @namespaces = namespaces || [] 15 | # case suite_or_files 16 | # when Test::Suite 17 | # @suite = suite_or_files 18 | # @files = suite_or_files.files 19 | # else 20 | # @suite = Test::Suite.new(suite_or_files) 21 | # @files = suite_or_files 22 | # end 23 | # #@canonical = @suite.canonical 24 | # @public = options[:public] 25 | #end 26 | 27 | # 28 | # New Coverage object. 29 | # 30 | # CoverageAnalyzer.new(suite, :MyApp, :public => true) 31 | # 32 | def initialize(files, options={}) 33 | @files = files 34 | 35 | @namespaces = [options[:namespaces]].flatten.compact 36 | @private = options[:private] 37 | @format = options[:format] 38 | @zealous = options[:zealous] 39 | 40 | @reporter = reporter_find(@format) 41 | 42 | reset_suite 43 | 44 | initialize_prerequisites(options) 45 | 46 | @canonical = Snapshot.capture #system #@suite.canonical 47 | 48 | #@suite = Lemon.suite 49 | #@suite = Lemon::TestSuite.new(files, :cover=>true) #@suite = suite 50 | #Lemon.suite = @suite 51 | 52 | files = files.map{ |f| Dir[f] }.flatten 53 | files = files.map{ |f| 54 | if File.directory?(f) 55 | Dir[File.join(f, '**/*.rb')] 56 | else 57 | f 58 | end 59 | }.flatten.uniq 60 | files = files.map{ |f| File.expand_path(f) } 61 | 62 | files.each{ |s| load s } #require s } 63 | 64 | @suite = $TEST_SUITE.dup 65 | end 66 | 67 | # 68 | # Load in prerequisites 69 | # 70 | def initialize_prerequisites(options) 71 | loadpath = [options[:loadpath] || []].compact.flatten 72 | requires = [options[:requires] || []].compact.flatten 73 | 74 | loadpath.each{ |path| $LOAD_PATH.unshift(path) } 75 | requires.each{ |path| require(path) } 76 | end 77 | 78 | # 79 | # 80 | # 81 | def reset_suite 82 | $TEST_SUITE = [] 83 | end 84 | 85 | # 86 | # 87 | # 88 | def suite 89 | @suite 90 | end 91 | 92 | # 93 | # Paths of lemon tests and/or ruby scripts to be compared and covered. 94 | # This can include directories too, in which case all .rb scripts below 95 | # then directory will be included. 96 | # 97 | attr :files 98 | 99 | # 100 | # Report format. 101 | # 102 | attr :format 103 | 104 | # 105 | # 106 | # 107 | attr :namespaces 108 | 109 | # 110 | ## Conical snapshot of system (before loading libraries to be covered). 111 | # 112 | #attr :canonical 113 | 114 | # 115 | # 116 | # 117 | def canonical 118 | @canonical #= Snapshot.capture 119 | end 120 | 121 | # 122 | # 123 | # 124 | def suite=(suite) 125 | raise ArgumentError unless TestSuite === suite 126 | @suite = suite 127 | end 128 | 129 | # 130 | # Only use public methods for coverage. 131 | # 132 | def public_only? 133 | !@private 134 | end 135 | 136 | # 137 | # 138 | # 139 | def private? 140 | @private 141 | end 142 | 143 | # 144 | # Include methods of uncovered cases in uncovered units. 145 | # 146 | def zealous? 147 | @zealous 148 | end 149 | 150 | # 151 | # 152 | # 153 | def namespaces 154 | @namespaces 155 | end 156 | 157 | # 158 | #def target_units 159 | # @target_units ||= target_system.units 160 | #end 161 | 162 | 163 | # Trigger a full set of calculations. 164 | def calculate 165 | uncovered_cases # that should be all it takes 166 | end 167 | 168 | # 169 | # 170 | # 171 | def covered_units 172 | @covered_units ||= ( 173 | list = [] 174 | suite.each do |test| 175 | covered_unit(test, list) 176 | end 177 | list.uniq 178 | ) 179 | end 180 | 181 | # 182 | # 183 | # 184 | def covered_unit(test, list) 185 | case test 186 | when Lemon::TestModule 187 | test.each do |t| 188 | covered_unit(t, list) 189 | end 190 | when Lemon::TestMethod 191 | list << Snapshot::Unit.new( 192 | test.context.target, 193 | test.target, 194 | :singleton=>test.class_method? 195 | ) 196 | else 197 | # ignore 198 | end 199 | end 200 | 201 | # 202 | # 203 | # 204 | def covered_namespaces 205 | @covered_namespaces ||= covered_units.map{ |u| u.namespace }.uniq 206 | end 207 | 208 | # 209 | # 210 | # 211 | def target_namespaces 212 | @target_namespaces ||= filter(covered_namespaces) 213 | end 214 | 215 | # 216 | # Target system snapshot. 217 | # 218 | def target 219 | @target ||= Snapshot.capture(target_namespaces) 220 | end 221 | 222 | # 223 | # Current system snapshot. 224 | # 225 | def current 226 | @current ||= Snapshot.capture 227 | end 228 | 229 | # 230 | # 231 | # 232 | def uncovered_units 233 | @uncovered_units ||= ( 234 | units = target.units 235 | if public_only? 236 | units = units.select{ |u| u.public? } 237 | end 238 | units -= (covered_units + canonical.units) 239 | units += uncovered_system.units if zealous? 240 | units 241 | ) 242 | end 243 | 244 | # 245 | # 246 | # 247 | def undefined_units 248 | @undefined_units ||= covered_units - target.units 249 | end 250 | 251 | # 252 | # List of modules/classes not covered. 253 | # 254 | def uncovered_cases 255 | @uncovered_cases ||= ( 256 | list = current.units - (target.units + canonical.units) 257 | list = list.map{ |u| u.namespace }.uniq 258 | list - canonical_cases 259 | ) 260 | end 261 | 262 | # 263 | # 264 | # 265 | def uncovered_system 266 | @uncovered_system ||= Snapshot.capture(uncovered_cases) 267 | end 268 | 269 | # 270 | # 271 | # 272 | def canonical_cases 273 | @canonical_cases ||= canonical.units.map{ |u| u.namespace }.uniq 274 | end 275 | 276 | # 277 | # 278 | # 279 | alias_method :covered, :covered_units 280 | alias_method :uncovered, :uncovered_units 281 | alias_method :undefined, :undefined_units 282 | 283 | # 284 | # Reset coverage data for recalcuation. 285 | # 286 | def reset! 287 | @covered_units = nil 288 | @covered_namespaces = nil 289 | @target_namespaces = nil 290 | @uncovered_units = nil 291 | @undefined_units = nil 292 | @target = nil 293 | @current = nil 294 | end 295 | 296 | # 297 | # Iterate over covered units. 298 | # 299 | def each(&block) 300 | covered_units.each(&block) 301 | end 302 | 303 | # 304 | # Number of covered units. 305 | # 306 | def size 307 | covered_units.size 308 | end 309 | 310 | # Iterate over +paths+ and use #load to bring in all +.rb+ scripts. 311 | #def load_system 312 | # files = [] 313 | # paths.map do |path| 314 | # if File.directory?(path) 315 | # files.concat(Dir[File.join(path, '**', '*.rb')]) 316 | # else 317 | # files.concat(Dir[path]) 318 | # end 319 | # end 320 | # files.each{ |file| load(file) } 321 | #end 322 | 323 | # # Snapshot of System to be covered. This takes a current snapshot 324 | # # of the system and removes the canonical snapshot or filters out 325 | # # everything but the selected namespace. 326 | # def system 327 | # if namespaces.empty? 328 | # snapshot - canonical 329 | # else 330 | # (snapshot - canonical).filter do |ofmod| 331 | # namespaces.any?{ |n| ofmod.name.start_with?(n.to_s) } 332 | # end 333 | # end 334 | # end 335 | 336 | # 337 | #def system 338 | # if namespaces.empty? 339 | # suite.coverage 340 | # else 341 | # suite.coverage.filter do |ofmod| 342 | # namespaces.any?{ |n| ofmod.name.start_with?(n.to_s) } 343 | # end 344 | # end 345 | #end 346 | 347 | private 348 | 349 | # 350 | # 351 | # 352 | def system(*namespaces) 353 | namespaces = nil if namespaces.empty? 354 | Snapshot.capture(namespaces) 355 | end 356 | 357 | # Get a snapshot of the system. 358 | #def snapshot 359 | # Snapshot.capture 360 | #end 361 | 362 | # 363 | # Filter namespaces. 364 | # 365 | def filter(ns) 366 | return ns if namespaces.nil? or namespaces.empty? 367 | #units = units.reject do |u| 368 | # /^Lemon::/ =~ u.namespace.to_s 369 | #end 370 | ns.select do |u| 371 | namespaces.any?{ |ns| /^#{ns}(::|$)/ =~ u.namespace.to_s } 372 | end 373 | end 374 | 375 | # 376 | # 377 | # 378 | def filter_units(units) 379 | return units if namespaces.nil? or namespaces.empty? 380 | #units = units.reject do |u| 381 | # /^Lemon::/ =~ u.namespace.to_s 382 | #end 383 | units = units.select do |u| 384 | namespaces.any? do |ns| 385 | /^#{ns}/ =~ u.namespace.to_s 386 | end 387 | end 388 | units 389 | end 390 | 391 | public 392 | 393 | # 394 | # 395 | # 396 | def render 397 | reporter.render 398 | end 399 | 400 | # 401 | # All output is handled by a reporter. 402 | # 403 | def reporter 404 | @reporter ||= reporter_find(format) 405 | end 406 | 407 | private 408 | 409 | DEFAULT_REPORTER = 'compact' 410 | 411 | # 412 | # 413 | # 414 | def reporter_find(format) 415 | format = format ? format.to_s.downcase : DEFAULT_REPORTER 416 | format = reporter_list.find do |name| 417 | /^#{format}/ =~ name 418 | end 419 | raise "unsupported format" unless format 420 | require "lemon/coverage/formats/#{format}" 421 | reporter = Lemon::CoverReports.const_get(format.capitalize) 422 | reporter.new(self) 423 | end 424 | 425 | # 426 | # 427 | # 428 | def reporter_list 429 | Dir[File.dirname(__FILE__) + '/formats/*.rb'].map do |rb| 430 | File.basename(rb).chomp('.rb') 431 | end 432 | end 433 | 434 | end 435 | 436 | end 437 | --------------------------------------------------------------------------------