testify 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/LICENSE +1 -1
- data/README.rdoc +100 -1
- data/Rakefile +20 -19
- data/VERSION +1 -0
- data/lib/framework.rb +115 -0
- data/lib/middleware.rb +20 -0
- data/lib/runner.rb +138 -0
- data/lib/test_result.rb +18 -0
- data/lib/testify.rb +41 -0
- data/samples/README +9 -0
- data/samples/awesome_runner.rb +28 -0
- data/samples/awesome_test_framework.rb +51 -0
- data/samples/awesome_tests/asplode.aws +3 -0
- data/samples/awesome_tests/failing.aws +3 -0
- data/samples/awesome_tests/passing.aws +3 -0
- data/samples/dots.rb +31 -0
- data/samples/summary.rb +19 -0
- data/spec/framework_spec.rb +81 -0
- data/spec/middleware_spec.rb +5 -0
- data/spec/runner_spec.rb +101 -0
- data/spec/sample_framework.rb +33 -0
- data/spec/sample_tests/failing_tests +7 -0
- data/spec/sample_tests/mixed_tests +11 -0
- data/spec/sample_tests/passing_tests +11 -0
- data/spec/sample_tests/test_helper +5 -0
- data/spec/spec_helper.rb +12 -7
- data/spec/test_result_spec.rb +11 -0
- data/spec/testify_spec.rb +19 -3
- metadata +72 -19
- data/.gitignore +0 -21
- data/spec/spec.opts +0 -1
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-c
|
data/Gemfile
ADDED
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -2,6 +2,105 @@
|
|
2
2
|
|
3
3
|
Testify is a test framework framework. Think "Rack for testing."
|
4
4
|
|
5
|
+
== Overview
|
6
|
+
|
7
|
+
Like Rack, Testify is typically going to be run as a stack of applications that
|
8
|
+
implement a +call+ method. In typical usage, the stack will be assembled by a
|
9
|
+
Runner and consist of a Framework and optionally some middleware, but this
|
10
|
+
isn't a requirement.
|
11
|
+
|
12
|
+
The source and detailed documentation can be found at
|
13
|
+
http://github.com/djspinmonkey/testify and
|
14
|
+
http://rdoc.info/projects/djspinmonkey/testify, respectively.
|
15
|
+
|
16
|
+
== Testify Applications
|
17
|
+
|
18
|
+
A Testify Application is any object that implements a +call+ method accepting a
|
19
|
+
single argument. The one argument will be a hash (canonically called env), and
|
20
|
+
the return value should be an array of TestResult objects.
|
21
|
+
|
22
|
+
=== The +env+ Hash
|
23
|
+
|
24
|
+
The +env+ hash should be an instance of Hash with one of the following keys
|
25
|
+
defined:
|
26
|
+
|
27
|
+
[:files] an array of filenames containing the tests
|
28
|
+
[:path] a path containing the files containing the tests
|
29
|
+
|
30
|
+
In addition, all of the following keys should be defined:
|
31
|
+
|
32
|
+
[:testify_version] An array of integers representing this version of Testify
|
33
|
+
[:hooks] A hash of callbacks - see below.
|
34
|
+
|
35
|
+
Given that Testify is still at a fairly early stage of development, it is quite
|
36
|
+
possible that more required keys will be added later.
|
37
|
+
|
38
|
+
=== Middleware
|
39
|
+
|
40
|
+
Middleware will generally either modify the env hash before the framework gets
|
41
|
+
it (eg, to execute only a subset of tests), or modify the TestResult objects on
|
42
|
+
the way back out (eg, to colorize the result text). If you want to respond to
|
43
|
+
a particular event as it happens (eg, send a Growl notification when a test
|
44
|
+
fails), it is probably best to do this by adding a hook rather than looking at
|
45
|
+
the returned TestResult objects, since call() will not return until all the
|
46
|
+
tests have finished running.
|
47
|
+
|
48
|
+
=== Frameworks
|
49
|
+
|
50
|
+
A Framework object is responsible for actually running the tests and generating
|
51
|
+
TestResult objects. Frameworks are required to respect all of the hooks
|
52
|
+
described below. Generally, Frameworks should inherit from
|
53
|
+
Testify::Framework::Base and should have at least one alias in order to benefit
|
54
|
+
from all of Testify's built-in functionality, but neither of these is a
|
55
|
+
requirement. (See the Aliasable module in the Classy gem for a more thorough
|
56
|
+
description of aliases. http://github.com/djspinmonkey/classy)
|
57
|
+
|
58
|
+
At least initially, most Framework classes will likely be adaptors for existing
|
59
|
+
test frameworks (RSpec, Test::Unit, etc).
|
60
|
+
|
61
|
+
=== Hooks
|
62
|
+
|
63
|
+
env[:hooks] is a hash containing arrays of callable objects (usually Procs,
|
64
|
+
but anything responding to +call+ will work) which will be called in specific
|
65
|
+
circumstances. The following keys should supported:
|
66
|
+
|
67
|
+
[:before_all] Will be called before running any tests. An array of Test
|
68
|
+
objects will be passed in. A middleware app might use this hook
|
69
|
+
instead of simply running immediately if it needed access to
|
70
|
+
individual tests instead of just files, or if it needed to run
|
71
|
+
after all other middleware. For example, if some other
|
72
|
+
middleware later in the stack were excluding some subset of tests
|
73
|
+
from running, you might need to be sure you were operating only
|
74
|
+
on the tests that would actually be run.
|
75
|
+
[:after_all] Will be called after running all tests. An array of TestResult
|
76
|
+
objects will be passed in. Middleware setting this hook should
|
77
|
+
probably just work with the array of TestResults returned from
|
78
|
+
calling the next Testify app on the stack instead, unless it
|
79
|
+
needs to run before the results pass through any other
|
80
|
+
middleware. Note that there is no guarantee another hook may not
|
81
|
+
be added prior to yours, however.
|
82
|
+
[:before_each] Will be called before running each individual test. A single
|
83
|
+
Test object will be passed in.
|
84
|
+
[:after_each] Will be called after running each individual test. A single
|
85
|
+
TestResult object will be passed in.
|
86
|
+
[:after_status] Points to a hash containing symbols as the keys, and arrays of
|
87
|
+
callable objects to be called whenever a TestResult is generated
|
88
|
+
with the status corresponding to that symbol. The TestResult
|
89
|
+
will be passed in.
|
90
|
+
|
91
|
+
If using the values provided by +Testify.env_defaults+, values for the first
|
92
|
+
four keys will be initialized to empty arrays, and +:after_status+ will be
|
93
|
+
initialized to an empty hash.
|
94
|
+
|
95
|
+
Like the +env+ hash, this is highly subject to change at this point.
|
96
|
+
|
97
|
+
== TODO
|
98
|
+
|
99
|
+
* Write adaptors for common test frameworks (rspec and minitest first, probably).
|
100
|
+
* Write an autotest-like Runner
|
101
|
+
* Write some useful middleware (growl notifications, colorized output, etc)
|
102
|
+
* World domination!
|
103
|
+
|
5
104
|
== Note on Patches/Pull Requests
|
6
105
|
|
7
106
|
* Fork the project.
|
@@ -15,4 +114,4 @@ Testify is a test framework framework. Think "Rack for testing."
|
|
15
114
|
|
16
115
|
== Copyright
|
17
116
|
|
18
|
-
Copyright (c)
|
117
|
+
Copyright (c) 2010 John Hyland. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
+
require 'rspec/core/rake_task'
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'jeweler'
|
@@ -9,7 +10,8 @@ begin
|
|
9
10
|
#gem.description = %Q{TODO: longer description of your gem}
|
10
11
|
gem.email = "github@djspinmonkey.com"
|
11
12
|
gem.homepage = "http://github.com/djspinmonkey/testify"
|
12
|
-
gem.authors = ["John Hyland"
|
13
|
+
gem.authors = ["John Hyland"]
|
14
|
+
gem.add_dependency "classy", ">= 1.0.0"
|
13
15
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
16
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
17
|
end
|
@@ -18,28 +20,27 @@ rescue LoadError
|
|
18
20
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
-
spec.libs << 'lib' << 'spec'
|
24
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
RSpec::Core::RakeTask.new do |t|
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
spec.rcov = true
|
26
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
27
|
+
t.rcov = true
|
28
|
+
t.rcov_opts = ['--exclude', 'spec']
|
31
29
|
end
|
32
30
|
|
33
|
-
task :spec
|
31
|
+
task :spec
|
34
32
|
|
35
33
|
task :default => :spec
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
rdoc.
|
45
|
-
|
35
|
+
# This is generating some annoying warnings - commented out for now until I
|
36
|
+
# figure out how to quiet it down.
|
37
|
+
#
|
38
|
+
#require 'rdoc/task'
|
39
|
+
#RDoc::Task.new do |rdoc|
|
40
|
+
# version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
#
|
42
|
+
# rdoc.rdoc_dir = 'rdoc'
|
43
|
+
# rdoc.title = "testify #{version}"
|
44
|
+
# rdoc.rdoc_files.include('README*')
|
45
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
#end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/framework.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module Testify
|
2
|
+
# The module which all Framework classes are recommended to inherit.
|
3
|
+
# Framework provides a few utility methods and a means to keep track of all
|
4
|
+
# Framework classes (via Aliasable). Framework objects are Testify apps, so
|
5
|
+
# subclasses must implement a +call+ method as described in the README.rdoc
|
6
|
+
# file. It is also good practice to declare at least one alias (see the
|
7
|
+
# Aliasable module in the Classy gem for more details).
|
8
|
+
#
|
9
|
+
module Framework
|
10
|
+
include Aliasable
|
11
|
+
|
12
|
+
# The default set of statuses
|
13
|
+
#
|
14
|
+
DEFAULT_STATUSES = [ :passed, :pending, :failed, :error ]
|
15
|
+
|
16
|
+
# Extend the ClassMethods module and set the default statuses.
|
17
|
+
#
|
18
|
+
# :nodoc:
|
19
|
+
#
|
20
|
+
def self.included( klass )
|
21
|
+
klass.extend Testify::Framework::ClassMethods
|
22
|
+
klass.statuses *Testify::Framework::DEFAULT_STATUSES.dup
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an array of absolute paths to each file defined by an +env+
|
27
|
+
# hash. The default implementation either returns the array of files, if
|
28
|
+
# :files is defined in the hash, or returns every file found in a
|
29
|
+
# traversal of the path in the :path key. Raises an exception if neither
|
30
|
+
# is defined, since that is not a valid +env+ hash.
|
31
|
+
#
|
32
|
+
# If a particular framework should only process files with names matching
|
33
|
+
# a particular glob pattern (eg, RSpec only wants files that match
|
34
|
+
# `*_spec.rb`), it can specify this with +file_pattern+.
|
35
|
+
#
|
36
|
+
def files( env )
|
37
|
+
return env[:files] if env.include? :files
|
38
|
+
raise(ArgumentError, "env hash must include either :files or :path") unless env.include? :path
|
39
|
+
|
40
|
+
file_glob = self.class.class_eval { @file_pattern } || '*'
|
41
|
+
Dir.glob(File.join(env[:path], '**', file_glob))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Run all the appropriate hooks before running any tests at all.
|
45
|
+
#
|
46
|
+
def run_before_all_hooks( env )
|
47
|
+
env[:hooks][:before_all].each { |hook| hook.call }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Run all the appropriate hooks before running each test.
|
51
|
+
#
|
52
|
+
def run_before_each_hooks( env )
|
53
|
+
env[:hooks][:before_each].each { |hook| hook.call }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Run all the appropriate hooks after running all the tests
|
57
|
+
#
|
58
|
+
def run_after_all_hooks( env, results )
|
59
|
+
env[:hooks][:after_all].each { |hook| hook.call(results) }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Run all the appropriate hooks after running each test individually
|
63
|
+
# (including the hooks for a particular status).
|
64
|
+
#
|
65
|
+
def run_after_each_hooks( env, result )
|
66
|
+
hooks = env[:hooks][:after_each]
|
67
|
+
hooks += env[:hooks][:after_status][result.status].to_a # .to_a in case it's nil
|
68
|
+
hooks.each { |hook| hook.call(result) }
|
69
|
+
end
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
# Gets and sets an array of symbols corresponding to the possible
|
73
|
+
# TestResult statuses that might be set by this Framework.
|
74
|
+
#
|
75
|
+
# For example, a framework with tests that can only pass or fail might
|
76
|
+
# like like this:
|
77
|
+
#
|
78
|
+
# class SomeFramework
|
79
|
+
# include Testify::Framework
|
80
|
+
#
|
81
|
+
# statuses :passed, :failed
|
82
|
+
#
|
83
|
+
# ...
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# SomeFramework.statuses # => [ :passed, :failed ]
|
87
|
+
#
|
88
|
+
def statuses( *new_statuses )
|
89
|
+
if new_statuses.any?
|
90
|
+
@statuses = new_statuses
|
91
|
+
end
|
92
|
+
@statuses
|
93
|
+
end
|
94
|
+
|
95
|
+
# Accepts a glob pattern that limits the paths returned by `#files`.
|
96
|
+
# Only paths with filenames that match this pattern will be returned by
|
97
|
+
# +.files+.
|
98
|
+
#
|
99
|
+
# For example, if all your framework's test files should end in
|
100
|
+
# _mytests.rb, you might do this:
|
101
|
+
#
|
102
|
+
# class MyTestFramework
|
103
|
+
# include Testify::Framework
|
104
|
+
#
|
105
|
+
# file_pattern '*_mytests.rb'
|
106
|
+
#
|
107
|
+
# ...
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
def file_pattern( pattern )
|
111
|
+
self.class_eval { @file_pattern = pattern }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/middleware.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Testify
|
2
|
+
module Middleware
|
3
|
+
include Aliasable
|
4
|
+
|
5
|
+
# By default, Testify middleware is initialized with only the next app on
|
6
|
+
# the stack. If you override this, you may also need to override
|
7
|
+
# Runner.construct_app_stack.
|
8
|
+
#
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
# By default, the middleware base class just calls the next app on the
|
14
|
+
# stack - you will almost certainly want to override this method.
|
15
|
+
#
|
16
|
+
def call(env)
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/runner.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Testify
|
2
|
+
|
3
|
+
# Runner is a Testify app intended to sit at the top of a Testify stack and
|
4
|
+
# provide a simple mechanism for running a set of tests. If you're writing
|
5
|
+
# an autotest-like application, Runner is what you would use. Runner uses
|
6
|
+
# Templatable from the Classy gem, so you can either subclass Runner and use
|
7
|
+
# DSL-like class methods to define its behavior, or instantiate it directly
|
8
|
+
# and configure the instance.
|
9
|
+
#
|
10
|
+
# For example,
|
11
|
+
#
|
12
|
+
# class SampleRunner
|
13
|
+
# include Testify::Runner
|
14
|
+
#
|
15
|
+
# framework :rspec
|
16
|
+
# middleware :growl, :red_green
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @runner = SampleRunner.new
|
20
|
+
#
|
21
|
+
# would be equivalent to
|
22
|
+
#
|
23
|
+
# @runner = Testify::Runner::Base.new
|
24
|
+
# @runner.framework = Testify::Framework::RspecAdaptor
|
25
|
+
# @runner.middleware = [Testify::Middleware::Growl, Testify::Middleware::RedGreen]
|
26
|
+
#
|
27
|
+
# Which approach is best depends on your needs, or you can use a mixture of
|
28
|
+
# the two.
|
29
|
+
#
|
30
|
+
module Runner
|
31
|
+
def self.included( klass )
|
32
|
+
klass.class_eval do
|
33
|
+
extend Templatable
|
34
|
+
extend Testify::Runner::ClassMethods
|
35
|
+
|
36
|
+
attr_accessor :status, :framework_instance, :test_results
|
37
|
+
templatable_attr :framework, :middleware
|
38
|
+
|
39
|
+
@@middleware = []
|
40
|
+
end
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows the framework to be specified on an instance of Runner. See +.framework+.
|
45
|
+
#
|
46
|
+
def framework= (fw)
|
47
|
+
@framework = Testify::Framework::Base.find(fw)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Allows the middleware array to be specified for an instance of Runner. See +.middleware+.
|
51
|
+
#
|
52
|
+
# Note: The class method accepts a list, but the instance method must be passed an actual array.
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
#
|
56
|
+
# runner = YourRunner.new
|
57
|
+
# runner.middleware = [:dots, :colorize]
|
58
|
+
#
|
59
|
+
def middleware=( middleware )
|
60
|
+
@middleware = middleware.map { |mw| Testify::Middleware.find(mw) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Constructs the stack of middleware and framework. If you are doing
|
64
|
+
# something unconventional (eg, creating middleware that requires
|
65
|
+
# additional initialization parameters), you may need to override this
|
66
|
+
# method.
|
67
|
+
#
|
68
|
+
def construct_app_stack
|
69
|
+
@framework_instance ||= framework.new
|
70
|
+
top_app = @framework_instance
|
71
|
+
|
72
|
+
middleware.each do |mw_class|
|
73
|
+
top_app = mw_class.new(top_app)
|
74
|
+
end
|
75
|
+
|
76
|
+
top_app
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the tests. Accepts a hash that will be merged in to the default env
|
80
|
+
# and passed to the Testify app stack.
|
81
|
+
#
|
82
|
+
def run( options = {} )
|
83
|
+
top_app = construct_app_stack
|
84
|
+
|
85
|
+
env = Testify.env_defaults.merge options
|
86
|
+
@test_results = top_app.call(env)
|
87
|
+
|
88
|
+
@status = @test_results.collect(&:status).max # XXX: Optimize?
|
89
|
+
|
90
|
+
@test_results
|
91
|
+
end
|
92
|
+
|
93
|
+
module ClassMethods
|
94
|
+
# Defines and/or returns the framework to use. This can be any class whose
|
95
|
+
# instances are Testify apps, and an instance of this class will be placed
|
96
|
+
# at the bottom of the Testify stack used by Runner#run. Typically, this
|
97
|
+
# will probably be a test framework built on Testify or (more likely, at
|
98
|
+
# least for now) a Testify adaptor for some existing framework. If the
|
99
|
+
# class descends from Testify::Framework::Base and defines an alias (eg,
|
100
|
+
# aka :rspec), you may use that alias instead of passing in the actual
|
101
|
+
# class.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
#
|
105
|
+
# class YourRunner < Testify::Runner::Base
|
106
|
+
# framework :rspec
|
107
|
+
# end
|
108
|
+
# @runner = YourRunner.new
|
109
|
+
# @runner.framework # <= Testify::Framework::RspecAdaptor
|
110
|
+
#
|
111
|
+
# Note that you can also specify the framework on an instance, using
|
112
|
+
# +#framework=+.
|
113
|
+
#
|
114
|
+
# @runner = Testify::Runner.new
|
115
|
+
# @runner.framework YourAwesomeTestifyFrameworkClass
|
116
|
+
#
|
117
|
+
def framework( fw = nil )
|
118
|
+
self.framework = Testify::Framework.find(fw) if fw
|
119
|
+
self.send(:class_variable_get, :@@framework)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Defines and/or returns the middleware to use, in the same manner as
|
123
|
+
# +.framework+. Note that this completely replaces any previous
|
124
|
+
# middleware specified for this class.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# class YourRunner < Testify::Runner::Base
|
129
|
+
# middleware :dots, :colorize
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
def middleware( *middlewares )
|
133
|
+
self.middleware = middlewares.map { |mw| Testify::Middleware.find(mw) } unless middlewares.empty?
|
134
|
+
self.send(:class_variable_get, :@@middleware)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/test_result.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Testify
|
2
|
+
|
3
|
+
# A TestResult object represents the results of running a single test,
|
4
|
+
# whatever that means in the context of your framework.
|
5
|
+
#
|
6
|
+
class TestResult
|
7
|
+
attr_accessor :status, :message, :file, :line
|
8
|
+
|
9
|
+
def initialize (options = {})
|
10
|
+
@status = options[:status]
|
11
|
+
@message = options[:message]
|
12
|
+
@file = options[:file]
|
13
|
+
@line = options[:line]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/testify.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# gems
|
2
|
+
require 'classy'
|
3
|
+
|
4
|
+
# Make sure we're grabbing the right version of everything.
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require 'framework'
|
8
|
+
require 'runner'
|
9
|
+
require 'middleware'
|
10
|
+
require 'test_result'
|
11
|
+
|
12
|
+
# Put the LOAD_PATH back the way it was.
|
13
|
+
$LOAD_PATH.shift
|
14
|
+
|
15
|
+
# The Testify module provides some general-purpose functions that aren't
|
16
|
+
# specific to any single component.
|
17
|
+
module Testify
|
18
|
+
|
19
|
+
# Provides some reasonable defaults to create a new env hash. This method
|
20
|
+
# does not fill in :path or :files, so you'll need to explicitly specify one
|
21
|
+
# or the other.
|
22
|
+
#
|
23
|
+
def self.env_defaults
|
24
|
+
{ :testify_version => Testify.version,
|
25
|
+
:hooks => { :before_all => [],
|
26
|
+
:after_all => [],
|
27
|
+
:before_each => [],
|
28
|
+
:after_each => [],
|
29
|
+
:after_status => {},
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the current version as an array. (Eg, version 1.2.3 would be
|
35
|
+
# returned as [1, 2, 3].)
|
36
|
+
#
|
37
|
+
def self.version
|
38
|
+
File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp.split('.').collect { |n| n.to_i }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/samples/README
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
This is a sample Testify-based setup. The framework is called (imaginatively
|
2
|
+
enough) AwesomeTestFramework - this is the same level as RSpec or minitest.
|
3
|
+
The runner is called AwesomeRunner - think of the 'rspec' command or
|
4
|
+
'autotest'. And for flavor we've added some middleware to output valuable
|
5
|
+
information while the tests run and append some footer text at the end.
|
6
|
+
|
7
|
+
To see it in action, just run this:
|
8
|
+
|
9
|
+
./awesome_runner.rb awesome_tests
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../lib/testify'
|
2
|
+
require_relative 'awesome_test_framework'
|
3
|
+
require_relative 'dots'
|
4
|
+
require_relative 'summary'
|
5
|
+
|
6
|
+
class AwesomeRunner
|
7
|
+
include Testify::Runner
|
8
|
+
|
9
|
+
# Specify the framework we want to use. This alias is defined in
|
10
|
+
# AwesomeTestFramework.
|
11
|
+
#
|
12
|
+
framework :awesome
|
13
|
+
|
14
|
+
# Specify the middleware. Again, this is defined in RadMiddleware.
|
15
|
+
#
|
16
|
+
middleware :summary, :dots
|
17
|
+
end
|
18
|
+
|
19
|
+
# Instantiate the runner and run the tests.
|
20
|
+
#
|
21
|
+
tests_path = File.join(File.dirname(__FILE__), 'awesome_tests')
|
22
|
+
@runner = AwesomeRunner.new
|
23
|
+
results = @runner.run(:path => tests_path)
|
24
|
+
|
25
|
+
#puts "#{results.size} tests run"
|
26
|
+
#results.each do |res|
|
27
|
+
# puts "#{res.status} - #{res.message}"
|
28
|
+
#end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# A sample test framework built using Testify.
|
2
|
+
#
|
3
|
+
class AwesomeTestFramework
|
4
|
+
include Testify::Framework
|
5
|
+
|
6
|
+
# Specify an alias for this framework.
|
7
|
+
#
|
8
|
+
aka :awesome
|
9
|
+
|
10
|
+
# These are the statuses this framework might return, in increasing order of severity.
|
11
|
+
#
|
12
|
+
statuses :pass, :fail, :error
|
13
|
+
|
14
|
+
# This is what a test file for this framework looks like.
|
15
|
+
#
|
16
|
+
file_pattern '*.aws'
|
17
|
+
|
18
|
+
# This is where you do your thing. In a real test framework, you'd probably
|
19
|
+
# be testing assertions and setting up contexts and generally making the
|
20
|
+
# magic happen here (or at least calling the libraries where the magic
|
21
|
+
# happens).
|
22
|
+
#
|
23
|
+
# In this example, we're just reading test results out of a file and calling
|
24
|
+
# the appropriate hooks.
|
25
|
+
#
|
26
|
+
def call( env )
|
27
|
+
results = []
|
28
|
+
|
29
|
+
run_before_all_hooks(env)
|
30
|
+
|
31
|
+
files(env).each do |file|
|
32
|
+
open file do |f|
|
33
|
+
f.lines.each_with_index do |line, line_number|
|
34
|
+
run_before_each_hooks(env)
|
35
|
+
|
36
|
+
message, status = * line.split(':')
|
37
|
+
status = status.strip.to_sym
|
38
|
+
result = Testify::TestResult.new( :file => file, :line => line_number, :message => message, :status => status )
|
39
|
+
results << result
|
40
|
+
|
41
|
+
run_after_each_hooks(env, result)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
run_after_all_hooks(env, results)
|
47
|
+
|
48
|
+
results
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/samples/dots.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Output a dot every time a test passes, an F when it fails, or a E on an
|
2
|
+
# error. On any other status, it prints a ?.
|
3
|
+
#
|
4
|
+
class Dots
|
5
|
+
include Testify::Middleware
|
6
|
+
|
7
|
+
aka :dots
|
8
|
+
|
9
|
+
def call( env )
|
10
|
+
# Output dots (et al) as each test runs.
|
11
|
+
#
|
12
|
+
env[:hooks][:after_each].push(lambda do |result|
|
13
|
+
case result.status
|
14
|
+
when :pass
|
15
|
+
print '.'
|
16
|
+
when :fail
|
17
|
+
print 'F'
|
18
|
+
when :error
|
19
|
+
print 'E'
|
20
|
+
else
|
21
|
+
print '?'
|
22
|
+
end
|
23
|
+
end)
|
24
|
+
|
25
|
+
# Output a couple newlines at the end after all the tests have run.
|
26
|
+
#
|
27
|
+
env[:hooks][:after_all].push(lambda { |ignored| puts; puts })
|
28
|
+
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
end
|
data/samples/summary.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Output a summary of the test results after they've all been run.
|
2
|
+
#
|
3
|
+
class Summary
|
4
|
+
include Testify::Middleware
|
5
|
+
|
6
|
+
aka :summary
|
7
|
+
|
8
|
+
def call( env )
|
9
|
+
results = @app.call(env)
|
10
|
+
|
11
|
+
summary = Hash.new(0)
|
12
|
+
results.each { |res| summary[res.status] += 1 }
|
13
|
+
|
14
|
+
puts "Ran #{results.size} tests in all"
|
15
|
+
summary.each { |status, count| puts "#{count} tests with status #{status}" }
|
16
|
+
|
17
|
+
results
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Testify::Framework" do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
Testify::Framework.forget_aliases
|
7
|
+
|
8
|
+
destroy_class :VanillaFramework
|
9
|
+
class VanillaFramework
|
10
|
+
include Testify::Framework
|
11
|
+
aka :vanilla
|
12
|
+
end
|
13
|
+
@framework = VanillaFramework.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to specify known aliases" do
|
17
|
+
Testify::Framework.find(:vanilla).should equal VanillaFramework
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise an ArgumentError if a given alias is already taken" do
|
21
|
+
lambda {
|
22
|
+
class ThisIsTheRepeat
|
23
|
+
include Testify::Framework
|
24
|
+
aka :vanilla
|
25
|
+
end
|
26
|
+
}.should raise_error(ArgumentError)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be able to specify a status ranking without affecting other Frameworks" do
|
31
|
+
class ChangedFramework
|
32
|
+
include Testify::Framework
|
33
|
+
statuses :passed, :failed, :exploded
|
34
|
+
end
|
35
|
+
|
36
|
+
VanillaFramework.statuses.should eql Testify::Framework::DEFAULT_STATUSES
|
37
|
+
ChangedFramework.statuses.should eql [ :passed, :failed, :exploded ]
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#files' do
|
41
|
+
before do
|
42
|
+
@test_path = File.join(File.dirname(__FILE__), "sample_tests")
|
43
|
+
all_files = ['failing_tests', 'mixed_tests', 'passing_tests', 'test_helper']
|
44
|
+
test_files = all_files - ['test_helper']
|
45
|
+
@all_paths = all_files.collect { |f| File.join(File.dirname(__FILE__), 'sample_tests', f) }
|
46
|
+
@test_paths = test_files.collect { |f| File.join(File.dirname(__FILE__), 'sample_tests', f) }
|
47
|
+
end
|
48
|
+
|
49
|
+
after do
|
50
|
+
VanillaFramework.file_pattern nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should find files specified by :path" do
|
54
|
+
env = { :path => @test_path }
|
55
|
+
@framework.files(env).sort.should eql @all_paths
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should find files specified by :files" do
|
59
|
+
env = { :files => @all_paths }
|
60
|
+
@framework.files(env).sort.should eql @all_paths
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise an ArgumentError if neither :files nor :path is defined" do
|
64
|
+
env = {}
|
65
|
+
lambda {
|
66
|
+
@framework.files(env)
|
67
|
+
}.should raise_exception ArgumentError
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should respect the .file_pattern setting" do
|
71
|
+
class VanillaFramework
|
72
|
+
file_pattern '*_tests'
|
73
|
+
end
|
74
|
+
|
75
|
+
env = { :path => @test_path }
|
76
|
+
@framework.files(env).sort.should eql @test_paths
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Testify::Runner" do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Testify::Middleware.forget_aliases
|
7
|
+
Testify::Framework.forget_aliases
|
8
|
+
|
9
|
+
destroy_class :SomeMiddleware
|
10
|
+
class SomeMiddleware
|
11
|
+
include Testify::Middleware
|
12
|
+
aka :some_middleware
|
13
|
+
end
|
14
|
+
|
15
|
+
destroy_class :SomeMoreMiddleware
|
16
|
+
class SomeMoreMiddleware
|
17
|
+
include Testify::Middleware
|
18
|
+
aka :more_middleware
|
19
|
+
end
|
20
|
+
|
21
|
+
destroy_class :SomeTestFramework
|
22
|
+
class SomeTestFramework
|
23
|
+
include Testify::Framework
|
24
|
+
|
25
|
+
aka :some_test_framework
|
26
|
+
attr_accessor :env
|
27
|
+
|
28
|
+
def call (env)
|
29
|
+
@env = env
|
30
|
+
[Testify::TestResult.new(:message => "Testing", :status => :pass)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
destroy_class :TestRunner
|
35
|
+
class TestRunner
|
36
|
+
include Testify::Runner
|
37
|
+
end
|
38
|
+
@runner = TestRunner.new
|
39
|
+
|
40
|
+
@test_path = File.expand_path(File.join(File.dirname(__FILE__), 'sample_tests'))
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to specify a test framework by alias" do
|
44
|
+
class TestRunner
|
45
|
+
framework :some_test_framework
|
46
|
+
end
|
47
|
+
|
48
|
+
TestRunner.framework.should eql SomeTestFramework
|
49
|
+
@runner.framework.should eql SomeTestFramework
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to specify a test framework by class" do
|
53
|
+
class TestRunner
|
54
|
+
framework SomeTestFramework
|
55
|
+
end
|
56
|
+
|
57
|
+
TestRunner.framework.should eql SomeTestFramework
|
58
|
+
@runner.framework.should eql SomeTestFramework
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should be able to specify middleware by alias" do
|
62
|
+
class TestRunner
|
63
|
+
middleware :some_middleware, :more_middleware
|
64
|
+
end
|
65
|
+
|
66
|
+
TestRunner.middleware.should eql [SomeMiddleware, SomeMoreMiddleware]
|
67
|
+
@runner.middleware.should eql [SomeMiddleware, SomeMoreMiddleware]
|
68
|
+
end
|
69
|
+
|
70
|
+
context "#run" do
|
71
|
+
it "should accept options and put them in the env hash" do
|
72
|
+
framework = SomeTestFramework.new
|
73
|
+
@runner.framework_instance = framework
|
74
|
+
@runner.run :foo => 'bar'
|
75
|
+
framework.env[:foo].should == 'bar'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "just created" do
|
80
|
+
it "should have a nil status" do
|
81
|
+
@runner.status.should be_nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with a test framework defined" do
|
86
|
+
before do
|
87
|
+
@runner.framework = SampleFramework
|
88
|
+
end
|
89
|
+
|
90
|
+
context "after running" do
|
91
|
+
before do
|
92
|
+
test_path = File.join(File.dirname(__FILE__), "sample_tests")
|
93
|
+
@runner.run :path => test_path
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have a status" do
|
97
|
+
@runner.status.should_not be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# A sample Testify framework intended to be used while testing Testify. A
|
2
|
+
# "test" consists of a line in the file, which should contain the status
|
3
|
+
# and optionally a colon and a message.
|
4
|
+
#
|
5
|
+
# Example test file:
|
6
|
+
# fail:The thing is not in the place.
|
7
|
+
# success
|
8
|
+
# pending:Need to implement cat napkins.
|
9
|
+
# success
|
10
|
+
#
|
11
|
+
class SampleFramework
|
12
|
+
include Testify::Framework
|
13
|
+
|
14
|
+
aka :sample
|
15
|
+
|
16
|
+
# Run the tests.
|
17
|
+
#
|
18
|
+
def call (env)
|
19
|
+
results = []
|
20
|
+
|
21
|
+
files(env).each do |file|
|
22
|
+
line_number = 0
|
23
|
+
File.open(file).each_line do |line|
|
24
|
+
line_number += 1
|
25
|
+
(status, message) = line.split(':')
|
26
|
+
results.push Testify::TestResult.new(:status => status.to_sym, :message => message, :file => file, :line => line_number)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
results
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require '
|
5
|
-
require 'spec/autorun'
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'testify')
|
2
|
+
require_relative '../lib/testify'
|
3
|
+
require_relative 'sample_framework'
|
4
|
+
require 'rspec'
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
RSpec.configure do |config|
|
7
|
+
end
|
8
|
+
|
9
|
+
# Doesn't work for anything inside a module (eg, if you try to remove
|
10
|
+
# Testify::Framework::RSpec this will totally blow up).
|
11
|
+
def destroy_class ( klass )
|
12
|
+
klass = klass.name.to_s if klass.kind_of? Class
|
13
|
+
Object.class_exec { remove_const klass } if Object.const_defined? klass
|
9
14
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "Testify::TestResult" do
|
4
|
+
it "should be instantializable with a hash of attributes" do
|
5
|
+
res = Testify::TestResult.new(:file => 'woo_tests.rb', :line => 42, :status => :error, :message => "Your test asploded.")
|
6
|
+
res.status.should == :error
|
7
|
+
res.message.should == "Your test asploded."
|
8
|
+
res.file.should == 'woo_tests.rb'
|
9
|
+
res.line.should == 42
|
10
|
+
end
|
11
|
+
end
|
data/spec/testify_spec.rb
CHANGED
@@ -1,7 +1,23 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__)
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
2
|
|
3
3
|
describe "Testify" do
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
context '.env_defaults' do
|
6
|
+
it "should provide an env hash with some default values" do
|
7
|
+
defaults = Testify.env_defaults
|
8
|
+
|
9
|
+
# Verify the values on these ones
|
10
|
+
defaults[:testify_version].should == Testify.version
|
11
|
+
defaults[:hooks].should == { :before_all => [], :after_all => [], :before_each => [], :after_each => [], :after_status => {} }
|
12
|
+
end
|
6
13
|
end
|
14
|
+
|
15
|
+
context '.version' do
|
16
|
+
it "should return an array of three integers" do
|
17
|
+
version = Testify.version
|
18
|
+
version.should have(3).parts
|
19
|
+
version.each { |n| n.should be_an Integer }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
end
|
metadata
CHANGED
@@ -1,28 +1,61 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- John Hyland
|
8
|
-
- Emily Price
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date:
|
14
|
-
default_executable:
|
13
|
+
date: 2011-10-31 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
16
|
+
name: classy
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: jeweler
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
18
34
|
type: :development
|
19
|
-
|
20
|
-
version_requirements:
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: classy
|
39
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.0.0
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
21
52
|
requirements:
|
22
53
|
- - ">="
|
23
54
|
- !ruby/object:Gem::Version
|
24
55
|
version: 1.2.9
|
25
|
-
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id004
|
26
59
|
description:
|
27
60
|
email: github@djspinmonkey.com
|
28
61
|
executables: []
|
@@ -34,42 +67,62 @@ extra_rdoc_files:
|
|
34
67
|
- README.rdoc
|
35
68
|
files:
|
36
69
|
- .document
|
37
|
-
- .
|
70
|
+
- .rspec
|
71
|
+
- Gemfile
|
38
72
|
- LICENSE
|
39
73
|
- README.rdoc
|
40
74
|
- Rakefile
|
75
|
+
- VERSION
|
76
|
+
- lib/framework.rb
|
77
|
+
- lib/middleware.rb
|
78
|
+
- lib/runner.rb
|
79
|
+
- lib/test_result.rb
|
41
80
|
- lib/testify.rb
|
42
|
-
-
|
81
|
+
- samples/README
|
82
|
+
- samples/awesome_runner.rb
|
83
|
+
- samples/awesome_test_framework.rb
|
84
|
+
- samples/awesome_tests/asplode.aws
|
85
|
+
- samples/awesome_tests/failing.aws
|
86
|
+
- samples/awesome_tests/passing.aws
|
87
|
+
- samples/dots.rb
|
88
|
+
- samples/summary.rb
|
89
|
+
- spec/framework_spec.rb
|
90
|
+
- spec/middleware_spec.rb
|
91
|
+
- spec/runner_spec.rb
|
92
|
+
- spec/sample_framework.rb
|
93
|
+
- spec/sample_tests/failing_tests
|
94
|
+
- spec/sample_tests/mixed_tests
|
95
|
+
- spec/sample_tests/passing_tests
|
96
|
+
- spec/sample_tests/test_helper
|
43
97
|
- spec/spec_helper.rb
|
98
|
+
- spec/test_result_spec.rb
|
44
99
|
- spec/testify_spec.rb
|
45
|
-
has_rdoc: true
|
46
100
|
homepage: http://github.com/djspinmonkey/testify
|
47
101
|
licenses: []
|
48
102
|
|
49
103
|
post_install_message:
|
50
|
-
rdoc_options:
|
51
|
-
|
104
|
+
rdoc_options: []
|
105
|
+
|
52
106
|
require_paths:
|
53
107
|
- lib
|
54
108
|
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
55
110
|
requirements:
|
56
111
|
- - ">="
|
57
112
|
- !ruby/object:Gem::Version
|
58
113
|
version: "0"
|
59
|
-
version:
|
60
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
61
116
|
requirements:
|
62
117
|
- - ">="
|
63
118
|
- !ruby/object:Gem::Version
|
64
119
|
version: "0"
|
65
|
-
version:
|
66
120
|
requirements: []
|
67
121
|
|
68
122
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.
|
123
|
+
rubygems_version: 1.8.6
|
70
124
|
signing_key:
|
71
125
|
specification_version: 3
|
72
126
|
summary: Testify is a test framework framework. Think "Rack for testing."
|
73
|
-
test_files:
|
74
|
-
|
75
|
-
- spec/testify_spec.rb
|
127
|
+
test_files: []
|
128
|
+
|
data/.gitignore
DELETED
data/spec/spec.opts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|