├── Gemfile ├── try ├── .test └── case_spectrum.rb ├── .gitignore ├── test ├── case_basic.rb ├── case_parametric_tests.rb ├── case_unit.rb ├── case_scope.rb └── case_setup.rb ├── .test ├── .yardopts ├── .travis.yml ├── lib ├── citron │ ├── world.rb │ ├── test_proc.rb │ └── test_case.rb └── citron.rb ├── Manifest.txt ├── work ├── benchmarks │ └── minitest_example.rb └── deprecated │ ├── test_advice.rb │ ├── test_teardown.rb │ └── test_setup.rb ├── History.md ├── Profile ├── Assembly ├── .ruby ├── License.txt ├── COPYING.md ├── README.md └── .gemspec /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /try/.test: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | require 'citron' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .yardoc 2 | doc 3 | log 4 | pkg 5 | site/api 6 | tmp 7 | web -------------------------------------------------------------------------------- /test/case_basic.rb: -------------------------------------------------------------------------------- 1 | testcase "basic test case" do 2 | 3 | test "basic test" do 4 | assert true 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /.test: -------------------------------------------------------------------------------- 1 | Test.run :default do |run| 2 | require 'ae' 3 | require 'citron' 4 | 5 | run.files << "test/case_*.rb" 6 | end 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title Citron 2 | --output-dir doc 3 | --protected 4 | --private 5 | --readme README.md 6 | lib 7 | - 8 | [A-Z]*.* 9 | 10 | -------------------------------------------------------------------------------- /test/case_parametric_tests.rb: -------------------------------------------------------------------------------- 1 | testcase "parametric tests" do 2 | 3 | test "example parametric test" do |x| 4 | x.assert.even? 5 | end 6 | 7 | ok 2 8 | ok 4 9 | 10 | end 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec rubytest" 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 | 13 | -------------------------------------------------------------------------------- /lib/citron/world.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # To add global helpers to test case context, you can 4 | # add that functionality to the Citron::World module. 5 | # 6 | class World #< Module 7 | end 8 | 9 | end 10 | 11 | -------------------------------------------------------------------------------- /test/case_unit.rb: -------------------------------------------------------------------------------- 1 | testcase Array do 2 | 3 | context '#join' do 4 | 5 | test "call Array#join" do 6 | [1,2,3].join.assert == "123" 7 | end 8 | 9 | test "unit should be class and method" do 10 | self.class.unit.assert == "Array#join" 11 | end 12 | 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yaropts bin lib spec test [A-Z]*.* 2 | .ruby 3 | lib/citron/test_case.rb 4 | lib/citron/test_proc.rb 5 | lib/citron/world.rb 6 | lib/citron.rb 7 | test/case_basic.rb 8 | test/case_parametric_tests.rb 9 | test/case_scope.rb 10 | test/case_setup.rb 11 | test/case_unit.rb 12 | COPYING.md 13 | README.md 14 | History.md 15 | License.txt 16 | -------------------------------------------------------------------------------- /test/case_scope.rb: -------------------------------------------------------------------------------- 1 | testcase "Test Scope" do 2 | 3 | def helper_method 4 | "helped!" 5 | end 6 | 7 | test "can use helper method" do 8 | helper_method.assert == "helped!" 9 | end 10 | 11 | context "sub-case inherits helpers" do 12 | 13 | test "can use helper method" do 14 | helper_method.assert == "helped!" 15 | end 16 | 17 | end 18 | 19 | test "test can't access case methods" do 20 | expect NameError do 21 | method(:ok) 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /lib/citron.rb: -------------------------------------------------------------------------------- 1 | $TEST_SUITE ||= [] 2 | 3 | module Citron 4 | require 'citron/world' 5 | require 'citron/test_proc' 6 | require 'citron/test_case' 7 | 8 | module DSL 9 | # 10 | # Define a general test case. 11 | # 12 | def test_case(label, *tags, &block) 13 | testcase = Citron::TestCase.context(label, *tags, &block) 14 | $TEST_SUITE << testcase.new 15 | end 16 | 17 | alias :TestCase :test_case 18 | alias :testcase :test_case 19 | end 20 | 21 | end 22 | 23 | extend Citron::DSL 24 | 25 | -------------------------------------------------------------------------------- /test/case_setup.rb: -------------------------------------------------------------------------------- 1 | testcase "Example of using setup in a testcase" do 2 | setup "the number one" do 3 | @x = 1 4 | end 5 | 6 | test "has setup without a topic" do 7 | @x.assert == 1 8 | end 9 | 10 | context "sub-case inherits parent setup" do 11 | test "has setup" do 12 | @x.assert == 1 13 | end 14 | end 15 | 16 | context "sub-case with setup override parent setup" do 17 | setup "has setup" do 18 | @y = 10 19 | end 20 | 21 | test "has setup" do 22 | @x.assert == nil 23 | @y.assert == 10 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /try/case_spectrum.rb: -------------------------------------------------------------------------------- 1 | TestCase "Show them how to Beat It" do 2 | 3 | # will fail 4 | test "show them how to funky" do 5 | "funky".assert != "funky" 6 | end 7 | 8 | # will pass 9 | test "show them what's right" do 10 | "right".assert == "right" 11 | end 12 | 13 | # will error 14 | test "no one wants to be defeated" do 15 | raise SyntaxError 16 | end 17 | 18 | # pending 19 | test "better do what you can" do 20 | raise NotImplementedError 21 | end 22 | 23 | # omit 24 | test "just beat it" do 25 | e = NotImplementedError.new 26 | e.set_assertion(true) 27 | raise e 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /work/benchmarks/minitest_example.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/unit' 2 | require 'minitest/autorun' 3 | 4 | class ShowThemHowToBeatIt < MiniTest::Unit::TestCase 5 | 6 | # will fail 7 | def test_show_them_how_to_funky 8 | refute_equal("funky", "funky") 9 | end 10 | 11 | # will pass 12 | def test_show_them_whats_right 13 | assert_equal("right", "right") 14 | end 15 | 16 | # will error 17 | def test_no_one_wants_to_be_defeated 18 | raise SyntaxError 19 | end 20 | 21 | # pending 22 | def test_better_do_what_you_can 23 | raise NotImplementedError 24 | end 25 | 26 | # omit 27 | def test_just_beat_it 28 | e = NotImplementedError.new 29 | #e.set_assertion(true) 30 | raise e 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ## 0.4.0 / 2012-02-28 4 | 5 | Reimplmented with a class-based model. This design is the traditional XUnit 6 | OOP design, so it is more fitting Citron's design goals. 7 | 8 | Changes: 9 | 10 | * Reimplement with class-based model. 11 | 12 | 13 | ## 0.3.0 / 2012-02-25 14 | 15 | This release cleans up the code, deprecates the complex Before and After 16 | advice, adds soem additional support for RubyTests lesser known features. 17 | 18 | Changes: 19 | 20 | * Deprecated Before and After advice. 21 | * Add support for #unit and #source_location. 22 | * Setup/terdown is inherited by subcases. 23 | 24 | 25 | ## 0.2.0 / 2011-08-11 26 | 27 | This is the first usable release of Citron. 28 | 29 | Changes: 30 | 31 | * General improvements. 32 | 33 | -------------------------------------------------------------------------------- /Profile: -------------------------------------------------------------------------------- 1 | --- 2 | name : citron 3 | version: 0.4.0 4 | title : Citron 5 | summary: Classic Unit Testing 6 | authors: 7 | - Thomas Sawyer 8 | 9 | description: 10 | Citron is a classical unit testing framework with 11 | a developer freindly DSL, runs on top of RubyTest 12 | and is BRASS compliant. 13 | 14 | resources: 15 | home: http://rubyworks.github.com/citron 16 | code: http://github.com/rubyworks/citron 17 | bugs: http://github.com/rubyworks/citron/issues 18 | chat: irc://chat.us.freenode.net/rubyworks 19 | mail: http://groups.google.com/groups/rubyworks-mailinglist 20 | 21 | repositories: 22 | upstream: git://github.com/proutils/citron.git 23 | 24 | requirements: 25 | - rubytest 26 | - ae 27 | - detroit (build) 28 | - reap (build) 29 | - qed (test) 30 | 31 | organization: RubyWorks 32 | 33 | copyrights: 34 | - 2011 Thomas Sawyer (BSD-2-Clause) 35 | 36 | 37 | -------------------------------------------------------------------------------- /work/deprecated/test_advice.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # Test Advice 4 | class TestAdvice 5 | 6 | # The test case to which this advice belongs. 7 | #attr :context 8 | 9 | # 10 | attr :table 11 | 12 | # New case instance. 13 | def initialize 14 | @table = Hash.new{ |h,k| h[k] = {} } 15 | end 16 | 17 | # 18 | def initialize_copy(original) 19 | @table = original.table.clone 20 | end 21 | 22 | # 23 | def [](type) 24 | @table[type.to_sym] 25 | end 26 | 27 | =begin 28 | # 29 | #def teardown=(procedure) 30 | # @teardown = procedure 31 | #end 32 | 33 | # Setup. 34 | def setup(scope=nil) 35 | if scope 36 | scope.instance_eval(&@setup) 37 | else 38 | @setup 39 | end 40 | end 41 | 42 | # Teardown. 43 | def teardown(scope=nil) 44 | if scope 45 | scope.instance_eval(&@teardown) if @teardown 46 | else 47 | @teardown 48 | end 49 | end 50 | 51 | # Returns the description with newlines removed. 52 | def to_s 53 | description.gsub(/\n/, ' ') 54 | end 55 | =end 56 | 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: web 4 | 5 | gem: 6 | active: true 7 | 8 | dnote: 9 | title: Source Notes 10 | labels: ~ 11 | output: log/notes.html 12 | 13 | yard: 14 | yardopts: true 15 | 16 | #qed: 17 | # active : false 18 | # files : ~ 19 | # #exclude : ~ 20 | # #loadpath: ~ 21 | # #requires: ~ 22 | # #live : false 23 | 24 | 25 | #qedoc: 26 | # active: false 27 | # files : spec/ 28 | # output: QED.rdoc 29 | 30 | vclog: 31 | active: false 32 | output: 33 | - log/changes.html 34 | - log/history.html 35 | 36 | email: 37 | file : ~ 38 | subject: ~ 39 | mailto : 40 | - ruby-talk@ruby-lang.org 41 | - rubyworks-mailinglist@googlegroups.com 42 | from : <%= ENV['EMAIL_ACCOUNT'] %> 43 | server : <%= ENV['EMAIL_SERVER'] %> 44 | port : <%= ENV['EMAIL_PORT'] %> 45 | account: <%= ENV['EMAIL_ACCOUNT'] %> 46 | domain : <%= ENV['EMAIL_DOMAIN'] %> 47 | login : <%= ENV['EMAIL_LOGIN'] %> 48 | secure : <%= ENV['EMAIL_SECURE'] %> 49 | 50 | #rdoc: 51 | # format: redfish 52 | # output: site/api 53 | # main: README.rdoc 54 | # include: 55 | # - 'lib' 56 | # - '[A-Z]*.*' 57 | # active: false 58 | 59 | 60 | -------------------------------------------------------------------------------- /work/deprecated/test_teardown.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # Ecapsulate a test case's teardown code. 4 | # 5 | class TestTeardown 6 | 7 | # 8 | # The test case to which this advice belong. 9 | # 10 | attr :context 11 | 12 | # 13 | # The setup procedures. 14 | # 15 | attr :procedures 16 | 17 | # 18 | # Initialize new Setup instance. 19 | # 20 | def initialize(context, &proc) 21 | @context = context 22 | @procedures = [] 23 | 24 | @procedures << proc if proc 25 | end 26 | 27 | # 28 | # Copy the teardown for a new context. 29 | # 30 | def copy(context) 31 | c = self.class.new(context) 32 | c.procedures = procedures 33 | c 34 | end 35 | 36 | # 37 | # Run teardown procedure in test scope. 38 | # 39 | def call(scope) 40 | procedures.each do |proc| 41 | scope.instance_eval(&proc) 42 | end 43 | end 44 | 45 | # 46 | # Add a teardown procedure. 47 | # 48 | def add(&proc) 49 | procedures << proc 50 | end 51 | 52 | protected 53 | 54 | def procedures=(procedures) 55 | @procedures = procedures 56 | end 57 | 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | - PROFILE 4 | - Profile 5 | authors: 6 | - name: Thomas Sawyer 7 | email: transfire@gmail.com 8 | copyrights: 9 | - holder: Thomas Sawyer 10 | year: '2011' 11 | license: BSD-2-Clause 12 | requirements: 13 | - name: rubytest 14 | - name: ae 15 | - name: detroit 16 | groups: 17 | - build 18 | development: true 19 | - name: reap 20 | groups: 21 | - build 22 | development: true 23 | - name: qed 24 | groups: 25 | - test 26 | development: true 27 | dependencies: [] 28 | alternatives: [] 29 | conflicts: [] 30 | repositories: 31 | - uri: git://github.com/proutils/citron.git 32 | scm: git 33 | name: upstream 34 | resources: 35 | home: http://rubyworks.github.com/citron 36 | code: http://github.com/rubyworks/citron 37 | bugs: http://github.com/rubyworks/citron/issues 38 | chat: irc://chat.us.freenode.net/rubyworks 39 | mail: http://groups.google.com/groups/rubyworks-mailinglist 40 | extra: {} 41 | load_path: 42 | - lib 43 | revision: 0 44 | name: citron 45 | title: Citron 46 | version: 0.4.0 47 | summary: Classic Unit Testing 48 | description: Citron is a classical unit testing framework with a developer freindly 49 | DSL, runs on top of RubyTest and is BRASS compliant. 50 | organization: RubyWorks 51 | date: '2012-02-25' 52 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Citron - Classic Unit Testing 2 | Copyright (c) 2011 Rubyworks (BSD-2-Clause license) 3 | All rights are expressly reserved. 4 | http://rubyworks.github.com/citron 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 | 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, 25 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /work/deprecated/test_setup.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # Ecapsulate a test case's setup code. 4 | # 5 | class TestSetup 6 | 7 | # 8 | # The test case to which this advice belong. 9 | # 10 | attr :context 11 | 12 | # 13 | # The setup procedures. 14 | # 15 | attr :procedures 16 | 17 | # 18 | # A brief description of the setup. 19 | # 20 | attr :label 21 | 22 | # 23 | # Initialize new Setup instance. 24 | # 25 | def initialize(context, label, &proc) 26 | @context = context 27 | @label = label.to_s 28 | @procedures = [] 29 | 30 | @procedures << proc if proc 31 | end 32 | 33 | # 34 | # Copy the setup for a new context. 35 | # 36 | def copy(context) 37 | c = self.class.new(context, label) 38 | c.procedures = procedures 39 | c 40 | end 41 | 42 | # 43 | # Run setup procedure in test scope. 44 | # 45 | def call(scope) 46 | procedures.each do |proc| 47 | scope.instance_eval(&proc) 48 | end 49 | end 50 | 51 | # 52 | # Returns the description with newlines removed. 53 | # 54 | def to_s 55 | label.gsub(/\n/, ' ') 56 | end 57 | 58 | # 59 | # Add a setup procedure. 60 | # 61 | def add(&proc) 62 | @procedures << proc 63 | end 64 | 65 | protected 66 | 67 | def procedures=(procedures) 68 | @procedures = procedures 69 | end 70 | 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | # COPYRIGHT 2 | 3 | ## NOTICES 4 | 5 | ### Citron 6 | 7 | Citron - Classic Unit Testing 8 | 2011 (c) Rubyworks (BSD-2-Clause license) 9 | All rights expressly reserved. 10 | http://rubyworks.github.com/citron 11 | 12 | ## LICENSES 13 | 14 | ### BSD-2-Clause 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | 1. Redistributions of source code must retain the above copyright notice, 20 | this list of conditions and the following disclaimer. 21 | 22 | 2. Redistributions in binary form must reproduce the above copyright 23 | notice, this list of conditions and the following disclaimer in the 24 | documentation and/or other materials provided with the distribution. 25 | 26 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 27 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 29 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 30 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 31 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 33 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 35 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Citron 2 | 3 | [Website](http://rubyworks.github.com/citron) / 4 | [Report Issue](http://github.com/rubyworks/citron/issues) / 5 | [IRC Channel](irc://chat.us.freenode.net/rubyworks) / 6 | [Mailing List](http://groups.google.com/groups/rubyworks-mailinglist) / 7 | [Development](http://github.com/rubyworks/citron) 8 | 9 | [![Build Status](https://secure.travis-ci.org/rubyworks/citron.png)](http://travis-ci.org/rubyworks/citron) 10 | 11 | 12 | ## Description 13 | 14 | Citron is a classical unit testing framework. It defines a simple 15 | domain language for creating traditionally modeled unit tests. 16 | 17 | 18 | ## Installation 19 | 20 | Using Rubygems simply install `citron`: 21 | 22 | $ gem install citron 23 | 24 | Citron depends on `ansi` for terminal colorization and `rubytest`, 25 | so those will be installed as well if they are not already. 26 | 27 | 28 | ## Instruction 29 | 30 | Citon tests are written as a collection of testcase and test blocks. 31 | Here is a fun example. We'll call the test file `test/test_beatit.rb`: 32 | 33 | ```ruby 34 | TestCase "Show them how to Beat It" do 35 | 36 | setup do 37 | @funky = "funky" 38 | @right = "right" 39 | end 40 | 41 | # fail 42 | test "show them how to funky" do 43 | @funky.assert != "funky" 44 | end 45 | 46 | # pass 47 | test "show them what's right" do 48 | @right.assert == "right" 49 | end 50 | 51 | # error 52 | test "no one wants to be defeated" do 53 | raise SyntaxError 54 | end 55 | 56 | # todo 57 | test "better do what you can" do 58 | raise NotImplementedError 59 | end 60 | 61 | end 62 | ``` 63 | 64 | Citron doesn't dictate the assertions system you use. In the above example, we are using 65 | the [A.E.](http://rubyworks.github.com/ae) assertion framework. You can use any [BRASS](http://rubyworks.github.com) 66 | compliant system you prefer. 67 | 68 | Citron is built on top of [RubyTest](http://rubyworks.github.com/rubytest). 69 | Jump over to its website to learn how to run tests and setup test run profiles. 70 | 71 | 72 | ## Copyrights 73 | 74 | Copyright (c) 2011 Rubyworks 75 | 76 | Citron is distributable according to the terms of the **FreeBSD** license. 77 | 78 | See COPYING.md for details. 79 | 80 | -------------------------------------------------------------------------------- /lib/citron/test_proc.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # Test procedure --what you would call a honest to goodness unit test. 4 | # 5 | class TestProc 6 | 7 | # New unit test procedure. 8 | # 9 | def initialize(options={}, &procedure) 10 | @context = options[:context] 11 | @label = options[:label] 12 | @tags = options[:tags] 13 | @skip = options[:skip] 14 | @file = options[:file] 15 | @line = options[:line] 16 | 17 | @procedure = procedure 18 | @tested = false 19 | end 20 | 21 | public 22 | 23 | # 24 | # The parent testcase to which this test belongs. 25 | # 26 | attr :context 27 | 28 | # 29 | # Alias for `#context`. 30 | # 31 | alias :parent :context 32 | 33 | # 34 | # Description of test. 35 | # 36 | attr :label 37 | 38 | # 39 | # Symbol list of tags. Trailing element may be Hash 40 | # of `symbol => object`. 41 | # 42 | attr :tags 43 | 44 | # 45 | # Test procedure, in which test assertions should be made. 46 | # 47 | attr :procedure 48 | 49 | # 50 | # 51 | # 52 | #def type 53 | # 'test' 54 | #end 55 | 56 | # 57 | # Whether to skip this test. 58 | # 59 | # @return [Boolean,String] 60 | # 61 | def skip? ; @skip ; end 62 | 63 | # 64 | # Set whether this test should be skipped of not. 65 | # 66 | # @param [Boolean,String] reason 67 | # Reason to skip, or simple boolean flag. 68 | # 69 | def skip=(reason) 70 | @skip = reason 71 | end 72 | 73 | # 74 | # @todo Is this necessary? 75 | # 76 | def tested? 77 | @tested 78 | end 79 | 80 | # 81 | # @todo Is this necessary? 82 | # 83 | def tested=(boolean) 84 | @tested = !!boolean 85 | end 86 | 87 | # 88 | # Test description. 89 | # 90 | # @return [String] 91 | # 92 | def to_s 93 | label.to_s 94 | end 95 | 96 | # 97 | # @return [TestSetup] setup 98 | # 99 | #def setup 100 | # @context.setup 101 | #end 102 | 103 | # TODO: possible to support topic? 104 | 105 | ## 106 | ## Ruby Test looks for `#topic` as the desciption of a test's setup. 107 | ## 108 | ## @return [String] Description of the setup. 109 | ## 110 | #def topic 111 | # setup.to_s 112 | #end 113 | 114 | # 115 | # Location of test definition. 116 | # 117 | def source_location 118 | [file, line] 119 | end 120 | 121 | # 122 | # Match test's label and/or tags. 123 | # 124 | # @param [String,Symbol,Regexp,Hash] match 125 | # Pattern to match against. 126 | # 127 | # @return [Boolean] 128 | # 129 | def match?(match) 130 | case match 131 | when Symbol 132 | tags.include?(match) 133 | when Hash 134 | if Hash === tags.last 135 | tags.last.any?{ |k,v| match[k] == v } 136 | end 137 | else 138 | match === label 139 | end 140 | end 141 | 142 | # 143 | # Run this test in context. 144 | # 145 | def call 146 | @scope.setup 147 | @scope.instance_eval(&procedure) 148 | @scope.teardown 149 | end 150 | 151 | # 152 | # Return copy with `@scope` set. 153 | # 154 | def for(scope) 155 | @scope = scope 156 | self.dup 157 | end 158 | 159 | ## 160 | ## Convert `#call` to Proc. 161 | ## 162 | ## @return [Proc] 163 | ## 164 | #def to_proc 165 | # lambda{ call } # procedure ? 166 | #end 167 | 168 | # 169 | #def set_proc(&proc) 170 | # @procedure = proc 171 | #end 172 | end 173 | 174 | end 175 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # For which revision of .ruby is this gemspec intended? 11 | REVISION = 0 12 | 13 | # 14 | PATTERNS = { 15 | :bin_files => 'bin/*', 16 | :lib_files => 'lib/{**/}*.rb', 17 | :ext_files => 'ext/{**/}extconf.rb', 18 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 19 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 20 | } 21 | 22 | # 23 | def self.instance 24 | new.to_gemspec 25 | end 26 | 27 | attr :metadata 28 | 29 | attr :manifest 30 | 31 | # 32 | def initialize 33 | @metadata = YAML.load_file('.ruby') 34 | @manifest = Dir.glob('manifest{,.txt}', File::FNM_CASEFOLD).first 35 | 36 | if @metadata['revision'].to_i != REVISION 37 | warn "You have the wrong revision. Trying anyway..." 38 | end 39 | end 40 | 41 | # 42 | def scm 43 | @scm ||= \ 44 | case 45 | when File.directory?('.git') 46 | :git 47 | end 48 | end 49 | 50 | # 51 | def files 52 | @files ||= \ 53 | #glob_files[patterns[:files]] 54 | case 55 | when manifest 56 | File.readlines(manifest). 57 | map{ |line| line.strip }. 58 | reject{ |line| line.empty? || line[0,1] == '#' } 59 | when scm == :git 60 | `git ls-files -z`.split("\0") 61 | else 62 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 63 | end.select{ |path| File.file?(path) } 64 | end 65 | 66 | # 67 | def glob_files(pattern) 68 | Dir.glob(pattern).select { |path| 69 | File.file?(path) && files.include?(path) 70 | } 71 | end 72 | 73 | # 74 | def patterns 75 | PATTERNS 76 | end 77 | 78 | # 79 | def executables 80 | @executables ||= \ 81 | glob_files(patterns[:bin_files]).map do |path| 82 | File.basename(path) 83 | end 84 | end 85 | 86 | def extensions 87 | @extensions ||= \ 88 | glob_files(patterns[:ext_files]).map do |path| 89 | File.basename(path) 90 | end 91 | end 92 | 93 | # 94 | def name 95 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 96 | end 97 | 98 | # 99 | def to_gemspec 100 | Gem::Specification.new do |gemspec| 101 | gemspec.name = name 102 | gemspec.version = metadata['version'] 103 | gemspec.summary = metadata['summary'] 104 | gemspec.description = metadata['description'] 105 | 106 | metadata['authors'].each do |author| 107 | gemspec.authors << author['name'] 108 | 109 | if author.has_key?('email') 110 | if gemspec.email 111 | gemspec.email << author['email'] 112 | else 113 | gemspec.email = [author['email']] 114 | end 115 | end 116 | end 117 | 118 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 119 | 120 | metadata['requirements'].each do |req| 121 | name = req['name'] 122 | version = req['version'] 123 | groups = req['groups'] || [] 124 | 125 | case version 126 | when /^(.*?)\+$/ 127 | version = ">= #{$1}" 128 | when /^(.*?)\-$/ 129 | version = "< #{$1}" 130 | when /^(.*?)\~$/ 131 | version = "~> #{$1}" 132 | end 133 | 134 | if groups.empty? or groups.include?('runtime') 135 | # populate runtime dependencies 136 | if gemspec.respond_to?(:add_runtime_dependency) 137 | gemspec.add_runtime_dependency(name,*version) 138 | else 139 | gemspec.add_dependency(name,*version) 140 | end 141 | else 142 | # populate development dependencies 143 | if gemspec.respond_to?(:add_development_dependency) 144 | gemspec.add_development_dependency(name,*version) 145 | else 146 | gemspec.add_dependency(name,*version) 147 | end 148 | end 149 | end 150 | 151 | # convert external dependencies into a requirements 152 | if metadata['external_dependencies'] 153 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 154 | metadata['external_dependencies'].each do |req| 155 | gemspec.requirements << req.to_s 156 | end 157 | end 158 | 159 | # determine homepage from resources 160 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 161 | gemspec.homepage = homepage.last if homepage 162 | 163 | gemspec.require_paths = metadata['load_path'] || ['lib'] 164 | gemspec.post_install_message = metadata['install_message'] 165 | 166 | # RubyGems specific metadata 167 | gemspec.files = files 168 | gemspec.extensions = extensions 169 | gemspec.executables = executables 170 | 171 | if Gem::VERSION < '1.7.' 172 | gemspec.default_executable = gemspec.executables.first 173 | end 174 | 175 | gemspec.test_files = glob_files(patterns[:test_files]) 176 | 177 | unless gemspec.files.include?('.document') 178 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 179 | end 180 | end 181 | end 182 | 183 | end #class GemSpec 184 | 185 | end 186 | 187 | DotRuby::GemSpec.instance 188 | -------------------------------------------------------------------------------- /lib/citron/test_case.rb: -------------------------------------------------------------------------------- 1 | module Citron 2 | 3 | # Test Case encapsulates a collection of 4 | # unit tests organized into groups of contexts. 5 | # 6 | class TestCase < World 7 | 8 | class << self 9 | 10 | # Brief description of the test case. 11 | attr :label 12 | 13 | # Symbol list of tags. Trailing element may be Hash 14 | # of `symbol => object`. 15 | attr :tags 16 | 17 | # List of tests and sub-cases. 18 | attr :tests 19 | 20 | # Code unit that is subject of test case. 21 | attr :unit 22 | 23 | # Initialize new TestCase. 24 | # 25 | def __set__(settings={}, &block) 26 | @label = settings[:label] 27 | @tags = settings[:tags] 28 | @skip = settings[:skip] 29 | 30 | @unit = calc_unit(@label) 31 | 32 | @tests = [] 33 | 34 | class_eval(&block) 35 | end 36 | 37 | # 38 | # 39 | # 40 | def calc_unit(label) 41 | case label 42 | when Module, Class 43 | @label 44 | when /^(\.|\#|\:\:)\w+/ 45 | if Module === superclass.unit 46 | [superclass.unit, @label].join('') 47 | else 48 | @label 49 | end 50 | end 51 | end 52 | 53 | # 54 | # Subclasses of TestCase can override this to describe 55 | # the type of test case they define. 56 | # 57 | # @return [String] 58 | # 59 | #def type 60 | # 'TestCase' 61 | #end 62 | 63 | # 64 | # Add new test or sub-case. 65 | # 66 | # @param [Class,TestProc] test_obejct 67 | # Test sub-case or procedure to add to this case. 68 | # 69 | def <<(test_object) 70 | @tests ||= [] 71 | @tests << test_object 72 | end 73 | 74 | # 75 | #def call 76 | # yield 77 | #end 78 | 79 | # 80 | # Is test case to be skipped? 81 | # 82 | # @return [Boolean,String] 83 | # If +false+ or +nil+ if not skipped, otherwise 84 | # +true+ or a string explain why to skip. 85 | # 86 | def skip? 87 | @skip 88 | end 89 | 90 | # 91 | # Set test case to be skipped. 92 | # 93 | # @param [Boolean,String] reason 94 | # Set to +false+ or +nil+ if not skipped, otherwise 95 | # +true+ or a string explain why to skip. 96 | # 97 | def skip=(reason) 98 | @skip = reason 99 | end 100 | 101 | # 102 | # Create a sub-case. 103 | # 104 | # @param [String] label 105 | # The breif description of the test case. 106 | # 107 | # @param [Array] tags 108 | # List of symbols with optional trailing `symbol=>object` hash. 109 | # These can be used as a means of filtering tests. 110 | # 111 | def Context(label, *tags, &block) 112 | context = Class.new(self) 113 | context.__set__( 114 | :skip => @_skip, 115 | :label => label, 116 | :tags => tags, 117 | &block 118 | ) 119 | 120 | self << context 121 | 122 | context 123 | end 124 | 125 | alias :context :Context 126 | 127 | # TODO: Alias context as concern ? 128 | #alias :concern, :Context 129 | #alias :Concern, :Context 130 | 131 | # 132 | # Create a test, or a parameterized test. 133 | # 134 | # @param [String] label 135 | # The breif description of the test case. 136 | # 137 | # @param [Array] tags 138 | # List of symbols with optional trailing `symbol=>object` hash. 139 | # These can be used as a means of filtering tests. 140 | # 141 | def Test(label=nil, *tags, &procedure) 142 | file, line, _ = *caller[0].split(':') 143 | 144 | settings = { 145 | :context => self, 146 | :skip => @_skip, 147 | :label => label, 148 | :tags => tags, 149 | :file => file, 150 | :line => line 151 | } 152 | 153 | if procedure.arity == 0 || (RUBY_VERSION < '1.9' && procedure.arity == -1) 154 | test = TestProc.new(settings, &procedure) 155 | 156 | self << test 157 | 158 | @_test = nil 159 | 160 | return test 161 | else 162 | @_test = [settings, procedure] 163 | end 164 | end 165 | 166 | alias :test :Test 167 | 168 | # 169 | # Actualize a parameterized test. 170 | # 171 | # @todo Better name than `Ok` ? 172 | # 173 | def Ok(*args) 174 | settings, procedure = *@_test 175 | 176 | test = TestProc.new(settings) do 177 | procedure.call(*args) 178 | end 179 | 180 | self << test 181 | 182 | return test 183 | end 184 | 185 | alias :ok :Ok 186 | 187 | # 188 | # Setup is used to set things up for each unit test. 189 | # The setup procedure is run before each unit. 190 | # 191 | # @param [String] label 192 | # A brief description of what the setup procedure sets-up. 193 | # 194 | def Setup(label=nil, &proc) 195 | define_method(:setup, &proc) 196 | # if the setup is reset, then so should the teardown 197 | define_method(:teardown){} 198 | end 199 | 200 | alias :setup :Setup 201 | 202 | # 203 | # Teardown procedure is used to clean-up after each unit test. 204 | # 205 | def Teardown(&proc) 206 | define_method(:teardown, &proc) 207 | end 208 | 209 | alias :teardown :Teardown 210 | 211 | # 212 | # Mark tests or sub-cases to be skipped. If block is given, then 213 | # tests defined within the block are skipped. Without a block 214 | # all subsquent tests defined in a context will be skipped. 215 | # 216 | # @param [Boolean,String] reason 217 | # Set to +false+ or +nil+ if not skipped, otherwise 218 | # +true+ or a string explain why to skip. 219 | # 220 | # @example 221 | # skip("awaiting new feature") do 222 | # test "some test" do 223 | # ... 224 | # end 225 | # end 226 | # 227 | # @example 228 | # skip("not on jruby") if jruby? 229 | # test "some test" do 230 | # ... 231 | # end 232 | # 233 | def Skip(reason=true, &block) 234 | if block 235 | @_skip = reason 236 | block.call if block 237 | @_skip = false 238 | else 239 | @_skip = reason 240 | end 241 | end 242 | 243 | alias :skip :Skip 244 | 245 | alias :inspect :to_s 246 | 247 | # 248 | # Test case label. 249 | # 250 | # @return [String] 251 | # 252 | def to_s 253 | label.to_s 254 | end 255 | 256 | end 257 | 258 | # 259 | # Iterate over each test and sub-case. 260 | # 261 | def each 262 | self.class.tests.each do |test_object| 263 | case test_object 264 | when Class #TestCase 265 | yield(test_object.new) 266 | when TestProc 267 | yield(test_object.for(self)) 268 | end 269 | end 270 | end 271 | 272 | # 273 | # Number of tests and sub-cases. 274 | # 275 | # @return [Fixnum] size 276 | # 277 | def size 278 | self.class.tests.size 279 | end 280 | 281 | # 282 | # Test case label. 283 | # 284 | # @return [String] 285 | # 286 | def to_s 287 | self.class.label.to_s 288 | end 289 | 290 | # 291 | # Dummy method for setup. 292 | # 293 | def setup 294 | end 295 | 296 | # 297 | # Dummy method for teardown. 298 | # 299 | def teardown 300 | end 301 | 302 | end 303 | 304 | end 305 | --------------------------------------------------------------------------------