uncool 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/HISTORY.rdoc +10 -0
- data/LICENSE +204 -0
- data/README.rdoc +90 -0
- data/bin/uncool-ruby +3 -0
- data/lib/uncool.rb +8 -0
- data/lib/uncool/analysis.rb +94 -0
- data/lib/uncool/app.rb +85 -0
- data/lib/uncool/autolog.rb +14 -0
- data/lib/uncool/cli.rb +168 -0
- data/lib/uncool/generator/abstract.rb +95 -0
- data/lib/uncool/generator/ko.rb +32 -0
- data/lib/uncool/generator/lemon.rb +31 -0
- data/lib/uncool/generator/qed.rb +30 -0
- data/lib/uncool/lemon.svg +39 -0
- data/lib/uncool/log.rb +17 -0
- data/lib/uncool/meta/data.rb +29 -0
- data/lib/uncool/meta/gemfile.yml +11 -0
- data/lib/uncool/meta/profile.yml +18 -0
- data/lib/uncool/report.rb +79 -0
- data/lib/uncool/report.rhtml +37 -0
- data/lib/uncool/syntax/ko.rb +9 -0
- data/lib/uncool/trace.rb +70 -0
- data/lib/uncool/unit.rb +73 -0
- data/meta/data.rb +29 -0
- data/meta/gemfile.yml +11 -0
- data/meta/profile.yml +18 -0
- data/test/features/coverage_feature.rb +14 -0
- data/test/features/generate_feature.rb +16 -0
- data/test/fixtures/example.rb +20 -0
- data/test/fixtures/example_run.rb +7 -0
- metadata +169 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
#if conf = Dir['{,.}config/uncool.yml'].first
|
2
|
+
# require 'yaml'
|
3
|
+
# opts = YAML.load(File.new(conf))
|
4
|
+
# output = opts['output'] || opts['out']
|
5
|
+
# namespaces = opts['spaces'] || opts['namespaces'] || opts['ns']
|
6
|
+
#else
|
7
|
+
output = ENV['out']
|
8
|
+
namespaces = ENV['ns'].split(',')
|
9
|
+
#end
|
10
|
+
|
11
|
+
require 'uncool/log'
|
12
|
+
|
13
|
+
Uncool.log(:output=>output, :namespaces=>namespaces)
|
14
|
+
|
data/lib/uncool/cli.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
module Uncool
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
# CLI Interface handle all lemon sub-commands.
|
6
|
+
class CLI
|
7
|
+
|
8
|
+
COMMANDS = ['coverage', 'generate']
|
9
|
+
|
10
|
+
#
|
11
|
+
def self.run(argv=ARGV)
|
12
|
+
new.run(argv)
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
def initialize(argv=ARGV)
|
17
|
+
@options = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
def options
|
22
|
+
@options
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
def run(argv)
|
27
|
+
if argv.include?('-g') or argv.include?('--generate')
|
28
|
+
cmd = 'generate'
|
29
|
+
else
|
30
|
+
cmd = 'coverage'
|
31
|
+
end
|
32
|
+
cmd = COMMANDS.find{ |c| /^#{cmd}/ =~ c }
|
33
|
+
__send__("#{cmd}_parse", argv)
|
34
|
+
__send__("#{cmd}", argv)
|
35
|
+
end
|
36
|
+
|
37
|
+
# C O V E R A G E
|
38
|
+
|
39
|
+
#
|
40
|
+
def coverage(scripts)
|
41
|
+
require 'uncool/app'
|
42
|
+
|
43
|
+
app = App.new(options)
|
44
|
+
|
45
|
+
app.log
|
46
|
+
|
47
|
+
scripts.each do |file|
|
48
|
+
require(file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
def coverage_parse(argv)
|
54
|
+
option_namespaces
|
55
|
+
option_private
|
56
|
+
option_output
|
57
|
+
option_format
|
58
|
+
#option_uncovered
|
59
|
+
option_loadpath
|
60
|
+
option_requires
|
61
|
+
|
62
|
+
option_parser.parse!(argv)
|
63
|
+
end
|
64
|
+
|
65
|
+
# G E N E R A T E
|
66
|
+
|
67
|
+
#
|
68
|
+
def generate(scripts)
|
69
|
+
require 'uncool/app'
|
70
|
+
|
71
|
+
app = App.new(options)
|
72
|
+
|
73
|
+
output = app.generate(scripts)
|
74
|
+
|
75
|
+
$stdout.puts(output)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
def generate_parse(argv)
|
80
|
+
option_generate
|
81
|
+
option_namespaces
|
82
|
+
option_framework
|
83
|
+
option_private
|
84
|
+
option_loadpath
|
85
|
+
option_requires
|
86
|
+
|
87
|
+
option_parser.parse!(argv)
|
88
|
+
end
|
89
|
+
|
90
|
+
# P A R S E R O P T I O N S
|
91
|
+
|
92
|
+
def option_namespaces
|
93
|
+
option_parser.on('-n', '--namespace NAME', 'add a namespace to output') do |name|
|
94
|
+
options[:namespaces] ||= []
|
95
|
+
options[:namespaces] << name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def option_framework
|
100
|
+
option_parser.on('-f', '--framework NAME', 'framework syntax to output') do |name|
|
101
|
+
options[:framework] = name.to_sym
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO: How feasible is it to parse tests of various frameworks to check "writ" coverage?
|
106
|
+
#def option_uncovered
|
107
|
+
# option_parser.on('-u', '--uncovered', 'include only uncovered tests') do
|
108
|
+
# options[:uncovered] = true
|
109
|
+
# end
|
110
|
+
#end
|
111
|
+
|
112
|
+
def option_private
|
113
|
+
option_parser.on('-p', '--private', 'include private and protected methods') do
|
114
|
+
options[:private] = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def option_output
|
119
|
+
option_parser.on('-o', '--output DIRECTORY', 'log directory') do |dir|
|
120
|
+
options[:output] = dir
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def option_format
|
125
|
+
option_parser.on('--format', '-f NAME', 'output format') do |name|
|
126
|
+
options[:format] = name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def option_loadpath
|
131
|
+
option_parser.on("-I PATH" , 'add directory to $LOAD_PATH') do |path|
|
132
|
+
paths = path.split(/[:;]/)
|
133
|
+
options[:loadpath] ||= []
|
134
|
+
options[:loadpath].concat(paths)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def option_requires
|
139
|
+
option_parser.on("-r FILE" , 'require file(s) (before doing anything else)') do |files|
|
140
|
+
files = files.split(/[:;]/)
|
141
|
+
options[:requires] ||= []
|
142
|
+
options[:requires].concat(files)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def option_generate
|
147
|
+
option_parser.on('-g' , '--generate', 'code generation mode') do
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def option_parser
|
152
|
+
@option_parser ||= (
|
153
|
+
OptionParser.new do |opt|
|
154
|
+
opt.on_tail("--debug" , 'turn on debugging mode') do
|
155
|
+
$DEBUG = true
|
156
|
+
end
|
157
|
+
opt.on_tail('-h', '--help', 'display this help messae') do
|
158
|
+
puts opt
|
159
|
+
exit 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'uncool/unit'
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
# Generator base class.
|
6
|
+
class GeneratorAbstract
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@namespaces = options[:namespaces] || []
|
10
|
+
@checklist = options[:checklist]
|
11
|
+
@options = options || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
def namespaces
|
16
|
+
@namespaces
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
def checklist
|
21
|
+
@checklist ||= default_checklist
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
def targets
|
26
|
+
@targets ||= namespaces.map{ |ns| eval(ns, TOPLEVEL_BINDING) }
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
def options
|
31
|
+
@options
|
32
|
+
end
|
33
|
+
|
34
|
+
# Include already covered methods.
|
35
|
+
def covered?
|
36
|
+
options[:covered]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Include private and protected methods?
|
40
|
+
def private?
|
41
|
+
options[:private]
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
def mapping
|
46
|
+
checklist.sort.group_by{ |mp, yes| mp.target }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Override this method in subclasses.
|
50
|
+
def generate
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
def default_checklist
|
55
|
+
list = []
|
56
|
+
targets.each do |target|
|
57
|
+
target.public_instance_methods(false).each do |meth|
|
58
|
+
list << Unit.new(target, meth)
|
59
|
+
end
|
60
|
+
if private?
|
61
|
+
target.protected_instance_methods(false).each do |meth|
|
62
|
+
list << Unit.new(target, meth, :access=>:protected)
|
63
|
+
end
|
64
|
+
target.private_instance_methods(false).each do |meth|
|
65
|
+
list << Unit.new(target, meth, :access=>:private)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
list
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
#
|
78
|
+
# # Generate code template.
|
79
|
+
# #
|
80
|
+
# def generate
|
81
|
+
# code = []
|
82
|
+
# system.each do |ofmod|
|
83
|
+
# next if ofmod.base.is_a?(Lemon::Test::Suite)
|
84
|
+
# code << "TestCase #{ofmod.base} do"
|
85
|
+
# ofmod.class_methods(public_only?).each do |meth|
|
86
|
+
# code << "\n MetaUnit :#{meth} => '' do\n raise Pending\n end"
|
87
|
+
# end
|
88
|
+
# ofmod.instance_methods(public_only?).each do |meth|
|
89
|
+
# code << "\n Unit :#{meth} => '' do\n raise Pending\n end"
|
90
|
+
# end
|
91
|
+
# code << "\nend\n"
|
92
|
+
# end
|
93
|
+
# code.join("\n")
|
94
|
+
# end
|
95
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'uncool/generator/abstract'
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
# KO test generator.
|
6
|
+
class GeneratorKO < GeneratorAbstract
|
7
|
+
|
8
|
+
#
|
9
|
+
def generate
|
10
|
+
code = []
|
11
|
+
mapping.each do |target, units|
|
12
|
+
#next if /Lemon::Test::Suite/ =~ target.to_s
|
13
|
+
code << "require 'lemon/syntax/ko'\n"
|
14
|
+
code << "testcase #{target} do"
|
15
|
+
units.each do |(unit, yes)|
|
16
|
+
next if unit.covered? and !covered?
|
17
|
+
next if unit.private? and !private?
|
18
|
+
if unit.function?
|
19
|
+
code << "\n metaunit :#{unit.method} do\n\n end"
|
20
|
+
else
|
21
|
+
code << "\n unit :#{unit.method} do\n\n end"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
code << "\nend\n"
|
25
|
+
end
|
26
|
+
code.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'uncool/generator/abstract'
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
# Lemon test generator.
|
6
|
+
class GeneratorLemon < GeneratorAbstract
|
7
|
+
|
8
|
+
#
|
9
|
+
def generate
|
10
|
+
code = []
|
11
|
+
mapping.each do |target, units|
|
12
|
+
#next if /Lemon::Test::Suite/ =~ target.to_s
|
13
|
+
code << "TestCase #{target} do"
|
14
|
+
units.each do |unit|
|
15
|
+
next if unit.covered? and !covered?
|
16
|
+
next if unit.private? and !private?
|
17
|
+
if unit.function?
|
18
|
+
code << "\n MetaUnit :#{unit.method} => '' do\n\n end"
|
19
|
+
else
|
20
|
+
code << "\n Unit :#{unit.method} => '' do\n\n end"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
code << "\nend\n"
|
24
|
+
end
|
25
|
+
code.join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'uncool/generator/abstract'
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
# QED test generator.
|
6
|
+
class GeneratorQED < GeneratorAbstract
|
7
|
+
|
8
|
+
#
|
9
|
+
def generate
|
10
|
+
code = []
|
11
|
+
mapping.each do |target, units|
|
12
|
+
#next if /Lemon::Test::Suite/ =~ target.to_s
|
13
|
+
code << "= #{target}\n"
|
14
|
+
units.each do |(unit, yes)|
|
15
|
+
next if unit.covered? and !covered?
|
16
|
+
next if unit.private? and !private?
|
17
|
+
if unit.function?
|
18
|
+
code << "== ::#{unit.method}\n\n"
|
19
|
+
else
|
20
|
+
code << "== ##{unit.method}\n\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
code.join("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
3
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
4
|
+
<svg id="svg1" sodipodi:version="0.32" inkscape:version="0.38.1" width="400.00000pt" height="400.00000pt" sodipodi:docbase="/var/www/html/svg_gallery/svg/fruits" sodipodi:docname="lemon.svg" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
5
|
+
<defs id="defs3">
|
6
|
+
<linearGradient id="linearGradient831">
|
7
|
+
<stop style="stop-color: rgb(255, 255, 0); stop-opacity: 1;" offset="0.0000000" id="stop832"/>
|
8
|
+
<stop style="stop-color: rgb(255, 227, 0); stop-opacity: 1;" offset="1.0000000" id="stop833"/>
|
9
|
+
</linearGradient>
|
10
|
+
<radialGradient xlink:href="#linearGradient831" id="radialGradient834" cx="0.33703703" cy="0.28358209" r="0.38183698" fx="0.33703703" fy="0.28358209"/>
|
11
|
+
</defs>
|
12
|
+
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.3195079" inkscape:cx="120.13018" inkscape:cy="204.09312" inkscape:window-width="910" inkscape:window-height="775" inkscape:window-x="119" inkscape:window-y="24"/>
|
13
|
+
<g id="g838">
|
14
|
+
<path style="fill: url(#radialGradient834) rgb(0, 0, 0); fill-rule: evenodd; stroke: rgb(0, 0, 0); stroke-width: 10; stroke-linejoin: round; stroke-dasharray: none;" d="M 68.708186,148.12939 C 228.69852,9.5810597 474.45687,146.15839 471.15810,289.65487 C 497.54826,315.22034 467.85933,343.25988 451.36548,350.68211 C 309.51838,525.51691 17.577254,382.02043 34.895796,237.69925 C 12.847026,174.32754 32.421718,155.55162 68.708186,148.12939 z " id="path827" sodipodi:nodetypes="ccccc"/>
|
15
|
+
<path style="fill-opacity: 0.133333; fill-rule: evenodd; stroke-width: 1pt;" d="M 44.330330,267.99099 C 65.580330,246.74099 68.080330,411.74099 378.08033,350.49099 C 371.83033,344.24099 338.08033,201.74099 458.08033,249.24099 C 504.95533,263.61599 358.70533,239.55349 410.58033,339.24099 C 418.62433,352.33698 416.20533,352.36599 438.08033,357.99099 C 305.58033,500.49099 46.830330,390.49099 44.330330,267.99099 z " id="path828" sodipodi:nodetypes="cccccc"/>
|
16
|
+
<path style="fill: rgb(255, 255, 255); fill-opacity: 0.7; fill-rule: evenodd; stroke-width: 1pt;" d="M 68.080330,155.83295 C 124.36962,90.490990 313.68568,43.977604 438.08033,203.33295 C 395.58033,249.58295 275.58033,49.582958 68.080330,155.83295 z " id="path829" sodipodi:nodetypes="ccc"/>
|
17
|
+
<path style="fill: rgb(255, 255, 255); fill-opacity: 0.7; fill-rule: evenodd; stroke-width: 1pt;" d="M 469.33033,295.49099 C 483.64639,312.99099 470.58033,332.99099 460.58033,330.49099 C 381.83033,309.24099 424.33033,287.99099 469.33033,295.49099 z " id="path830" sodipodi:nodetypes="ccc"/>
|
18
|
+
</g>
|
19
|
+
|
20
|
+
<rdf:RDF xmlns="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
21
|
+
<Work rdf:about="">
|
22
|
+
<dc:title>Clipart by Nicu Buculei - pear</dc:title>
|
23
|
+
<dc:rights>
|
24
|
+
<Agent>
|
25
|
+
<dc:title>Nicu Buculei</dc:title>
|
26
|
+
</Agent>
|
27
|
+
</dc:rights>
|
28
|
+
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
29
|
+
<license rdf:resource="http://web.resource.org/cc/PublicDomain"/>
|
30
|
+
</Work>
|
31
|
+
|
32
|
+
<License rdf:about="http://web.resource.org/cc/PublicDomain">
|
33
|
+
<permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
|
34
|
+
<permits rdf:resource="http://web.resource.org/cc/Distribution"/>
|
35
|
+
<permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
|
36
|
+
</License>
|
37
|
+
|
38
|
+
</rdf:RDF>
|
39
|
+
</svg>
|
data/lib/uncool/log.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'uncool/app'
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
# Run coverage trace and log results.
|
6
|
+
#
|
7
|
+
# targets = ENV['squeeze'].split(',')
|
8
|
+
# Lemon.log(targets, :output=>'log')
|
9
|
+
#
|
10
|
+
# NOTE: This sets up an at_exit routine.
|
11
|
+
def self.log(options={})
|
12
|
+
app = App.new(options)
|
13
|
+
app.log
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Object.__send__(:remove_const, :VERSION) if Object.const_defined?(:VERSION) # becuase Ruby 1.8~ gets in the way
|
2
|
+
|
3
|
+
module Uncool
|
4
|
+
|
5
|
+
def self.__DIR__
|
6
|
+
File.dirname(__FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.gemfile
|
10
|
+
@gemfile ||= (
|
11
|
+
require 'yaml'
|
12
|
+
YAML.load(File.new(__DIR__ + '/gemfile.yml'))
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.profile
|
17
|
+
@profile ||= (
|
18
|
+
require 'yaml'
|
19
|
+
YAML.load(File.new(__DIR__ + '/profile.yml'))
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.const_missing(name)
|
24
|
+
name = name.to_s.downcase
|
25
|
+
gemfile[name] || profile[name]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|