rutema 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -2
- data/Manifest.txt +10 -1
- data/Rakefile +4 -2
- data/bin/rutemah +9 -0
- data/lib/rutema/configuration.rb +29 -3
- data/lib/rutema/historian.rb +94 -0
- data/lib/rutema/reporter.rb +99 -0
- data/lib/rutema/reporter_ar.rb +149 -0
- data/lib/rutema/specification.rb +5 -2
- data/lib/rutema/system.rb +20 -101
- data/test/samples/rutemah_config.rb +3 -0
- data/test/test_configuration.rb +3 -3
- data/test/test_historian.rb +32 -0
- data/test/test_reporter.rb +74 -0
- data/test/test_system.rb +0 -20
- metadata +42 -8
data/History.txt
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
== 0.3 / 2007-05-30
|
2
|
+
* Reporter implementations moved to own file
|
3
|
+
* Reporter interface changed (Runner stati now passed directly as a name indexed Hash, specifications passed as well)
|
4
|
+
* ActiveRecordReporter with SQLite DB added
|
5
|
+
* rutemah (the Historian) added in tools. The Historian reads from ActiveRecordReporter databases
|
6
|
+
|
1
7
|
== 0.2 / 2007-05-21
|
2
8
|
* bin/ files included in gem
|
3
9
|
* rutemax: missing configuration file is now checked before passed to the system
|
10
|
+
|
4
11
|
== 0.1 / 2007-05-21
|
5
12
|
* A running system!
|
6
13
|
* Basic XML parser with echo, command and prompt
|
7
14
|
* email reporting
|
8
15
|
* rutemax application
|
9
|
-
|
10
|
-
|
data/Manifest.txt
CHANGED
@@ -3,15 +3,24 @@ Manifest.txt
|
|
3
3
|
README.txt
|
4
4
|
COPYING.txt
|
5
5
|
Rakefile
|
6
|
+
bin/rutemax
|
7
|
+
bin/rutemah
|
6
8
|
lib/rutema/configuration.rb
|
7
9
|
lib/rutema/specification.rb
|
8
10
|
lib/rutema/system.rb
|
11
|
+
lib/rutema/reporter.rb
|
12
|
+
lib/rutema/reporter_ar.rb
|
13
|
+
lib/rutema/historian.rb
|
14
|
+
test/test_configuration.rb
|
9
15
|
test/test_specification.rb
|
10
16
|
test/test_system.rb
|
17
|
+
test/test_historian.rb
|
18
|
+
test/test_reporter.rb
|
11
19
|
test/samples/check.spec
|
12
20
|
test/samples/setup.spec
|
13
21
|
test/samples/teardown.spec
|
14
22
|
test/samples/test.spec
|
15
23
|
test/samples/tests/no_title.spec
|
16
24
|
test/samples/tests/sample.spec
|
17
|
-
test/samples/valid_config.rb
|
25
|
+
test/samples/valid_config.rb
|
26
|
+
test/samples/rutemah_config.rb
|
data/Rakefile
CHANGED
@@ -14,10 +14,12 @@ Hoe.new('rutema', "#{Rutema::VERSION_MAJOR}.#{Rutema::VERSION_MINOR}") do |p|
|
|
14
14
|
p.description = p.paragraphs_of('README.txt', 1..5).join("\n\n")
|
15
15
|
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
16
16
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
17
|
-
p.extra_deps<<['patir',">=0.
|
17
|
+
p.extra_deps<<['patir',">=0.4"]
|
18
18
|
p.extra_deps<<['highline']
|
19
19
|
p.extra_deps<<['mailfactory']
|
20
|
-
p.
|
20
|
+
p.extra_deps<<['activerecord']
|
21
|
+
p.extra_deps<<['ruport']
|
22
|
+
p.spec_extras={:executables=>["rutemax","rutemah"],
|
21
23
|
:default_executable=>"rutemax"}
|
22
24
|
end
|
23
25
|
|
data/bin/rutemah
ADDED
data/lib/rutema/configuration.rb
CHANGED
@@ -6,7 +6,7 @@ require 'patir/configuration'
|
|
6
6
|
|
7
7
|
module Rutema
|
8
8
|
#This module defines the "configuration directives" used in the configuration of RutemaX
|
9
|
-
module
|
9
|
+
module RutemaXConfiguration
|
10
10
|
#Adds a path to a tools to the tools hash of the configuration
|
11
11
|
#
|
12
12
|
#Required keys:
|
@@ -58,6 +58,7 @@ module Rutema
|
|
58
58
|
|
59
59
|
#Hash values for passing data to the system. It's supposed to be used in the reporters and contain
|
60
60
|
#values such as verson numbers, tester names etc.
|
61
|
+
#
|
61
62
|
def context_data= definition
|
62
63
|
@context||=Hash.new
|
63
64
|
raise Patir::ConfigurationException,"Only accepting hash values as context_data" unless definition.kind_of?(Hash)
|
@@ -110,9 +111,20 @@ module Rutema
|
|
110
111
|
end
|
111
112
|
end
|
112
113
|
|
114
|
+
#This module defines the "configuration directives" used in the configuration of RutemaH
|
115
|
+
module RutemaHConfiguration
|
116
|
+
#The configuration keys for the historian's database
|
117
|
+
def source= definition
|
118
|
+
if File.exists?(definition)
|
119
|
+
@source=File.expand_path(definition)
|
120
|
+
else
|
121
|
+
raise Patir::ConfigurationException,"DB file '#{definition}' not found"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
113
125
|
#This class reads a RutemaX configuration file
|
114
|
-
class
|
115
|
-
include
|
126
|
+
class RutemaXConfigurator<Patir::Configurator
|
127
|
+
include RutemaXConfiguration
|
116
128
|
def initialize config_file,logger=nil
|
117
129
|
@reporters=Array.new
|
118
130
|
@context=Hash.new
|
@@ -139,4 +151,18 @@ module Rutema
|
|
139
151
|
return conf
|
140
152
|
end
|
141
153
|
end
|
154
|
+
|
155
|
+
#This class reads a RutemaH configuration file
|
156
|
+
class RutemaHConfigurator<RutemaXConfigurator
|
157
|
+
include RutemaHConfiguration
|
158
|
+
def initialize config_file,logger=nil
|
159
|
+
super(config_file,logger)
|
160
|
+
end
|
161
|
+
|
162
|
+
def configuration
|
163
|
+
cfg=super
|
164
|
+
cfg.source=@source
|
165
|
+
return cfg
|
166
|
+
end
|
167
|
+
end
|
142
168
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rutema/reporter_ar'
|
2
|
+
require 'rutema/system'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require "ruport"
|
6
|
+
|
7
|
+
module Rutema
|
8
|
+
#The "historian" application class
|
9
|
+
#
|
10
|
+
#RutemaH provides reports for test results over time using the data stored by the ActiveRecordReporter
|
11
|
+
class RutemaH
|
12
|
+
require 'optparse'
|
13
|
+
def initialize command_line_args
|
14
|
+
parse_command_line(command_line_args)
|
15
|
+
logger=Patir.setup_logger(@log_file)
|
16
|
+
begin
|
17
|
+
raise "No configuration file defined!" if !@config_file
|
18
|
+
configuration=RutemaHConfigurator.new(@config_file,logger).configuration
|
19
|
+
historian=Historian.new(configuration,logger)
|
20
|
+
historian.history(@command)
|
21
|
+
rescue Patir::ConfigurationException
|
22
|
+
logger.debug($!)
|
23
|
+
logger.fatal("Configuration error '#{$!.message}'")
|
24
|
+
exit 1
|
25
|
+
rescue
|
26
|
+
logger.debug($!)
|
27
|
+
logger.fatal("#{$!.message}")
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
private
|
32
|
+
def parse_command_line args
|
33
|
+
args.options do |opt|
|
34
|
+
opt.on("Options:")
|
35
|
+
opt.on("--debug", "-d","Turns on debug messages") { $DEBUG=true }
|
36
|
+
opt.on("--config FILE", "-c FILE",String,"Loads the configuration from FILE") { |@config_file|}
|
37
|
+
opt.on("--log FILE", "-l FILE",String,"Redirects the log output to FILE") { |@log_file|}
|
38
|
+
opt.on("-V", "--version","Displays the version") { $stdout.puts("v#{VERSION_MAJOR}.#{VERSION_MINOR}");exit 0 }
|
39
|
+
opt.on("--help", "-h", "-?", "This text") { $stdout.puts opt; exit 0 }
|
40
|
+
opt.parse!
|
41
|
+
#and now the rest
|
42
|
+
if args.empty?
|
43
|
+
$stdout.puts opt
|
44
|
+
exit 0
|
45
|
+
else
|
46
|
+
@command=args.shift
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end#parse_command_line
|
50
|
+
end
|
51
|
+
|
52
|
+
class Historian
|
53
|
+
def initialize configuration,logger=nil
|
54
|
+
@logger=logger
|
55
|
+
@logger||=Patir.setup_logger
|
56
|
+
@configuration=configuration
|
57
|
+
connect()
|
58
|
+
end
|
59
|
+
|
60
|
+
def history mode
|
61
|
+
case mode
|
62
|
+
when "all"
|
63
|
+
DB::Run.find(:all).each do |run|
|
64
|
+
puts "#{run.id}-----"
|
65
|
+
table=DB::Scenario.report_table(:all,:except=>["id","run_id","number"],:conditions => "run_id = '#{run.id}'")
|
66
|
+
puts beautify(table)
|
67
|
+
end
|
68
|
+
when String
|
69
|
+
per_spec(mode)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private
|
73
|
+
def beautify table
|
74
|
+
table.reorder("name","status","attended","version","start_time","stop_time")
|
75
|
+
table.replace_column("version") {|r| r.version ? r.version : "N/A"}
|
76
|
+
table.replace_column("attended") {|r| r.attended=="t" ? "yes" : "no"}
|
77
|
+
table.replace_column("start_time") {|r| r.stop_time ? r.start_time : nil}
|
78
|
+
return table
|
79
|
+
end
|
80
|
+
def per_spec spec_name
|
81
|
+
table=DB::Scenario.report_table(:all,:conditions=>"name = '#{spec_name}'",:except=>["id","run_id","number"])
|
82
|
+
if table.empty?
|
83
|
+
@logger.warn("No test run records found for #{spec_name}")
|
84
|
+
else
|
85
|
+
puts beautify(table)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
def connect
|
89
|
+
ActiveRecord::Base.logger = @logger
|
90
|
+
@logger.info("Connecting with database '#{@configuration.source}'")
|
91
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3",:database => @configuration.source)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Copyright (c) 2007 Vassilis Rizopoulos. All rights reserved.
|
2
|
+
require 'rutema/specification'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'mailfactory'
|
6
|
+
|
7
|
+
module Rutema
|
8
|
+
#Reporter is meant as a base class for reporter classes.
|
9
|
+
class Reporter
|
10
|
+
#params should be a Hash containing the parameters used to initialize the class
|
11
|
+
def initialize params
|
12
|
+
end
|
13
|
+
|
14
|
+
#Coordinator will pass the Rutema __configuration__ giving you access to the context which can contain data like headings and build numbers to use in the report. It will also pass the specifications used in the last run so that data like the title and the specification version can be used.
|
15
|
+
#
|
16
|
+
#runner_states is an Hash of Patir::CommandSeqeunceStatus indexed by the specification name containing the stati of the last run (so it contains all the Scenario stati for the loaded tests)
|
17
|
+
#
|
18
|
+
#parse_errors is an Array of {:filename,:error} hashes containing the errors encountered by the parser when loading the specifications
|
19
|
+
def report specifications,runner_states,parse_errors,configuration
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#The following configuration keys are used by EmailReporter:
|
25
|
+
#
|
26
|
+
#:server - the smtp server to use
|
27
|
+
#
|
28
|
+
#:port - the port to use (defaults to 25)
|
29
|
+
#
|
30
|
+
#:sender - the sender of the email (defaults to rutema@domain)
|
31
|
+
#
|
32
|
+
#:recipients - an array of strings with the recipients of the report emails
|
33
|
+
#
|
34
|
+
#The :logger key is set by the Coordinator
|
35
|
+
#
|
36
|
+
#Customization keys:
|
37
|
+
#
|
38
|
+
#:subject - the string of this key will be prefixed as a subject for the email
|
39
|
+
class EmailReporter
|
40
|
+
attr_reader :last_message
|
41
|
+
def initialize definition
|
42
|
+
#get the logger
|
43
|
+
@logger=definition[:logger]
|
44
|
+
@logger||=Patir.setup_logger
|
45
|
+
#extract the parameters from the definition
|
46
|
+
#address and port of the smtp server
|
47
|
+
@server=definition[:server]
|
48
|
+
@port=definition[:port]
|
49
|
+
@port||=25
|
50
|
+
#the domain we're coming from
|
51
|
+
@domain=definition[:domain]
|
52
|
+
#construct the mail factory object
|
53
|
+
@mail = MailFactory.new()
|
54
|
+
@mail.from = definition[:sender]
|
55
|
+
@mail.from||="rutema@#{@domain}"
|
56
|
+
@recipients=definition[:recipients]
|
57
|
+
@mail.to=@recipients
|
58
|
+
#customize
|
59
|
+
@subject=definition[:subject]
|
60
|
+
@subject||=""
|
61
|
+
#this is a way to test without sending
|
62
|
+
@dummy=true if definition[:dummy]
|
63
|
+
@logger.info("Reporter '#{self.to_s}' registered")
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
list=@recipients.join(', ')
|
68
|
+
"EmailReporter - #{@server}:#{@port} from #{@mail.from} to #{list}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def report specifications,runner_states,parse_errors,configuration
|
72
|
+
@mail.subject = "#{@subject}"
|
73
|
+
@mail.text = Rutema.text_report(runner_states,parse_errors)
|
74
|
+
begin
|
75
|
+
if @mail.to.empty?
|
76
|
+
@logger.error("No recipients for the report mail")
|
77
|
+
else
|
78
|
+
#
|
79
|
+
#~ if @password
|
80
|
+
#~ #if a password is defined, use cram_md5 authentication
|
81
|
+
#~ else
|
82
|
+
Net::SMTP.start(@server, @port, @domain) {|smtp| smtp.sendmail(@mail.to_s(),@mail.from,mail_to)} unless @dummy
|
83
|
+
#~ end
|
84
|
+
end#recipients empty
|
85
|
+
rescue
|
86
|
+
@logger.error("Sending of email report failed: #{$!}")
|
87
|
+
end
|
88
|
+
@mail.to_s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private
|
92
|
+
def self.text_report runner_states,parse_errors
|
93
|
+
msg="Parse errors: #{parse_errors.size}"
|
94
|
+
parse_errors.each{|e| msg<<"\n\t#{e[:filename]}"}
|
95
|
+
msg<<"\nCurrent run:\nScenarios: #{runner_states.size}"
|
96
|
+
# msg<<"\n#{runner_status.summary}"
|
97
|
+
return msg
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Copyright (c) 2007 Vassilis Rizopoulos. All rights reserved.
|
2
|
+
require 'rutema/reporter'
|
3
|
+
require 'yaml'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_record'
|
6
|
+
require 'ruport/acts_as_reportable'
|
7
|
+
require 'patir/command'
|
8
|
+
|
9
|
+
#this fixes the AR Logger hack that annoys me sooooo much
|
10
|
+
class Logger
|
11
|
+
private
|
12
|
+
def format_message(severity, datetime, progname, msg)
|
13
|
+
(@formatter || @default_formatter).call(severity, datetime, progname, msg)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Rutema
|
18
|
+
#The DB module provides the ActiveRecord model for Rutema
|
19
|
+
module DB
|
20
|
+
#This is the schema for the AR database used to store test results
|
21
|
+
#
|
22
|
+
#We store the RutemaConfiguration#context for every run so that reports for past runs can be recreated without running the actual tests again.
|
23
|
+
class Schema<ActiveRecord::Migration
|
24
|
+
def self.up
|
25
|
+
create_table :runs do |t|
|
26
|
+
t.column :context, :string
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :scenarios do |t|
|
30
|
+
t.column :name, :string, :null=>false
|
31
|
+
t.column :run_id,:integer, :null=>false
|
32
|
+
t.column :attended,:bool, :null=>false
|
33
|
+
t.column :status, :string,:null=>false
|
34
|
+
t.column :number,:integer
|
35
|
+
t.column :start_time, :datetime,:null=>false
|
36
|
+
t.column :stop_time, :datetime
|
37
|
+
t.column :version, :string
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :steps do |t|
|
41
|
+
t.column :scenario_id,:integer, :null=>false
|
42
|
+
t.column :name, :string
|
43
|
+
t.column :number, :integer,:null=>false
|
44
|
+
t.column :status, :string,:null=>false
|
45
|
+
t.column :output, :text
|
46
|
+
t.column :error, :text
|
47
|
+
t.column :duration, :time
|
48
|
+
end
|
49
|
+
|
50
|
+
create_table :parse_errors do |t|
|
51
|
+
t.column :filename, :string,:null=>false
|
52
|
+
t.column :error, :string
|
53
|
+
t.column :run_id,:integer, :null=>false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Run<ActiveRecord::Base
|
59
|
+
has_many :scenarios
|
60
|
+
has_many :parse_errors
|
61
|
+
acts_as_reportable
|
62
|
+
end
|
63
|
+
|
64
|
+
class Scenario<ActiveRecord::Base
|
65
|
+
belongs_to :run
|
66
|
+
has_many :steps
|
67
|
+
acts_as_reportable
|
68
|
+
end
|
69
|
+
|
70
|
+
class Step<ActiveRecord::Base
|
71
|
+
belongs_to :scenario
|
72
|
+
acts_as_reportable
|
73
|
+
end
|
74
|
+
|
75
|
+
class ParseError<ActiveRecord::Base
|
76
|
+
belongs_to :run
|
77
|
+
acts_as_reportable
|
78
|
+
end
|
79
|
+
end
|
80
|
+
#The ActiveRecordReporter will store the results of a test run in a database using ActiveRecord.
|
81
|
+
#
|
82
|
+
#The current implementation uses SQLite as the DBMS until we get enough time to implement the rest of the configuration.
|
83
|
+
#
|
84
|
+
#We store the RutemaConfiguration#context for every run so that reports for past runs can be recreated without running the actual tests again.
|
85
|
+
class ActiveRecordReporter<Reporter
|
86
|
+
def initialize definition
|
87
|
+
@definition=definition
|
88
|
+
@logger=definition[:logger]
|
89
|
+
@logger||=Patir.setup_logger
|
90
|
+
raise "No database file defined" unless definition[:dbfile]
|
91
|
+
connect
|
92
|
+
end
|
93
|
+
|
94
|
+
def report specifications,runner_states,parse_errors,configuration
|
95
|
+
run_entry=DB::Run.new
|
96
|
+
if configuration && configuration.context
|
97
|
+
run_entry.context=YAML.dump(configuration.context)
|
98
|
+
end
|
99
|
+
parse_errors.each do |pe|
|
100
|
+
er=DB::ParseError.new()
|
101
|
+
er.filename=pe[:filename]
|
102
|
+
er.error=pe[:error]
|
103
|
+
run_entry.parse_errors<<er
|
104
|
+
end
|
105
|
+
runner_states.each do |scenario|
|
106
|
+
sc=DB::Scenario.new
|
107
|
+
sc.name=scenario.sequence_name
|
108
|
+
sc.number=scenario.sequence_id
|
109
|
+
sc.start_time=scenario.start_time
|
110
|
+
sc.stop_time=scenario.stop_time
|
111
|
+
sc.status=scenario.status.to_s
|
112
|
+
#get the specification for this scenario
|
113
|
+
spec=specifications[scenario.sequence_name]
|
114
|
+
if spec
|
115
|
+
sc.version=spec.version
|
116
|
+
else
|
117
|
+
@logger.error("Could not find specification for #{scenario.sequence_name}")
|
118
|
+
end
|
119
|
+
if scenario.strategy==:attended
|
120
|
+
sc.attended=true
|
121
|
+
else
|
122
|
+
sc.attended=false
|
123
|
+
end
|
124
|
+
scenario.step_states.each do |number,step|
|
125
|
+
st=DB::Step.new
|
126
|
+
st.name=step[:name]
|
127
|
+
st.number=number
|
128
|
+
st.status=step[:status].to_s
|
129
|
+
st.output=step[:output]
|
130
|
+
st.error=step[:error]
|
131
|
+
st.duration=step[:duration]
|
132
|
+
sc.steps<<st
|
133
|
+
end
|
134
|
+
run_entry.scenarios<<sc
|
135
|
+
end
|
136
|
+
run_entry.save
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def connect
|
141
|
+
ActiveRecord::Base.logger = @logger
|
142
|
+
@logger.info("Connecting with database '#{@definition[:dbfile]}'")
|
143
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
|
144
|
+
:database => @definition[:dbfile])
|
145
|
+
DB::Schema.migrate(:up) unless File.exists?(@definition[:dbfile])
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/rutema/specification.rb
CHANGED
@@ -57,6 +57,8 @@ module Rutema
|
|
57
57
|
#
|
58
58
|
#:requirements - An Array of String. The idea is that the strings can lead you back to the requirements specification that is tested here.
|
59
59
|
#
|
60
|
+
#:version - The version of this specification
|
61
|
+
#
|
60
62
|
#Default values are empty strings and arrays. (scenario is nil)
|
61
63
|
def initialize *args
|
62
64
|
params=args[0] if args
|
@@ -68,7 +70,7 @@ module Rutema
|
|
68
70
|
@attributes[:title]||=""
|
69
71
|
@attributes[:filename]||=""
|
70
72
|
@attributes[:description]||=""
|
71
|
-
@scenario=TestScenario.new
|
73
|
+
@scenario=TestScenario.new(@attributes[:version])
|
72
74
|
@requirements||=Array.new
|
73
75
|
end
|
74
76
|
end
|
@@ -86,7 +88,8 @@ module Rutema
|
|
86
88
|
include SpecificationElement
|
87
89
|
attr_reader :steps
|
88
90
|
|
89
|
-
def initialize
|
91
|
+
def initialize version=nil
|
92
|
+
@version=version
|
90
93
|
@attended=false
|
91
94
|
@steps=Array.new
|
92
95
|
end
|
data/lib/rutema/system.rb
CHANGED
@@ -4,22 +4,23 @@ require 'rexml/document'
|
|
4
4
|
|
5
5
|
require 'rutema/specification'
|
6
6
|
require 'rutema/configuration'
|
7
|
+
require 'rutema/reporter'
|
8
|
+
|
7
9
|
|
8
10
|
require 'rubygems'
|
9
11
|
require 'highline'
|
10
|
-
require 'mailfactory'
|
11
12
|
require 'patir/command'
|
12
13
|
|
13
14
|
module Rutema
|
14
15
|
VERSION_MAJOR=0
|
15
|
-
VERSION_MINOR=
|
16
|
+
VERSION_MINOR=3
|
16
17
|
#Is raised when an error is found in a specification
|
17
18
|
class ParserError<RuntimeError
|
18
19
|
end
|
19
20
|
|
20
21
|
#Base class that bombs out when used....
|
21
22
|
class SpecificationParser
|
22
|
-
def parse_specification
|
23
|
+
def parse_specification param
|
23
24
|
raise ParserError,"not implemented. You should derive a parser implementation from SpecificationParser!"
|
24
25
|
end
|
25
26
|
end
|
@@ -176,6 +177,7 @@ module Rutema
|
|
176
177
|
end
|
177
178
|
end
|
178
179
|
|
180
|
+
#This class coordinates parsing, execution and reporting of test specifications
|
179
181
|
class Coordinator
|
180
182
|
attr_accessor :configuration,:parse_errors,:parsed_files
|
181
183
|
def initialize configuration,logger=nil
|
@@ -190,6 +192,8 @@ module Rutema
|
|
190
192
|
@configuration.tests.collect!{|t| File.expand_path(t)}
|
191
193
|
@runner=create_runner
|
192
194
|
@parsed_files=Array.new
|
195
|
+
#this will hold any specifications that are succesfully parsed.
|
196
|
+
@specifications=Hash.new
|
193
197
|
end
|
194
198
|
#Runs a set of tests
|
195
199
|
#
|
@@ -224,11 +228,10 @@ module Rutema
|
|
224
228
|
#It then joins the threads and returns when all of them are finished.
|
225
229
|
def report
|
226
230
|
threads=Array.new
|
227
|
-
runner_status=Patir::CommandSequenceStatus.new("",@runner.states.values)
|
228
231
|
#get the runner stati and the configuration and give it to the reporters
|
229
232
|
@reporters.each do |reporter|
|
230
|
-
threads<<Thread.new(reporter
|
231
|
-
@logger.debug(reporter.report(status,perrors,configuration))
|
233
|
+
threads<<Thread.new(reporter,@specifications,@runner.states.values,@parse_errors,@configuration) do |reporter,specs,status,perrors,configuration|
|
234
|
+
@logger.debug(reporter.report(specs,status,perrors,configuration))
|
232
235
|
end
|
233
236
|
end
|
234
237
|
threads.each do |t|
|
@@ -238,8 +241,7 @@ module Rutema
|
|
238
241
|
end
|
239
242
|
|
240
243
|
def to_s
|
241
|
-
|
242
|
-
"Parsed #{@parsed_files.size} files\n#{Rutema.text_report(runner_status,@parse_errors)}"
|
244
|
+
"Parsed #{@parsed_files.size} files\n#{Rutema.text_report(@runner.states.values,@parse_errors)}"
|
243
245
|
end
|
244
246
|
private
|
245
247
|
def instantiate_class definition
|
@@ -277,7 +279,8 @@ module Rutema
|
|
277
279
|
begin
|
278
280
|
@parsed_files<<filename
|
279
281
|
@parsed_files.uniq!
|
280
|
-
|
282
|
+
spec=@parser.parse_specification(spec_file)
|
283
|
+
@specifications[spec.name]=spec
|
281
284
|
rescue ParserError
|
282
285
|
@logger.error("Error parsing '#{spec_file}': #{$!.message}")
|
283
286
|
@parse_errors<<{:filename=>filename,:error=>$!.message}
|
@@ -385,6 +388,11 @@ module Rutema
|
|
385
388
|
@logger.warn("Attended scenario cannot be run in unattended mode")
|
386
389
|
state..status=:warning
|
387
390
|
else
|
391
|
+
if scenario.attended?
|
392
|
+
state.strategy=:attended
|
393
|
+
else
|
394
|
+
state.strategy=:unattended
|
395
|
+
end
|
388
396
|
stps=scenario.steps
|
389
397
|
if stps.empty?
|
390
398
|
@logger.warn("Scenario #{name} contains no steps")
|
@@ -435,7 +443,7 @@ module Rutema
|
|
435
443
|
end
|
436
444
|
end
|
437
445
|
|
438
|
-
#The application class
|
446
|
+
#The "executioner" application class
|
439
447
|
class RutemaX
|
440
448
|
require 'optparse'
|
441
449
|
def initialize command_line_args
|
@@ -443,7 +451,7 @@ module Rutema
|
|
443
451
|
@logger=Patir.setup_logger(@log_file)
|
444
452
|
begin
|
445
453
|
raise "No configuration file defined!" if !@config_file
|
446
|
-
@configuration=
|
454
|
+
@configuration=RutemaXConfigurator.new(@config_file,@logger).configuration
|
447
455
|
@coordinator=Coordinator.new(@configuration,@logger)
|
448
456
|
application_flow
|
449
457
|
rescue Patir::ConfigurationException
|
@@ -490,6 +498,7 @@ module Rutema
|
|
490
498
|
end
|
491
499
|
end
|
492
500
|
end
|
501
|
+
|
493
502
|
def application_flow
|
494
503
|
if @check
|
495
504
|
#run just the check test
|
@@ -506,95 +515,5 @@ module Rutema
|
|
506
515
|
@coordinator.report
|
507
516
|
end
|
508
517
|
end
|
509
|
-
#Reporter is meant as a base class for reporter classes.
|
510
|
-
class Reporter
|
511
|
-
#params should be a Hash containing the parameters used to initialize the class
|
512
|
-
def initialize params
|
513
|
-
end
|
514
|
-
|
515
|
-
#Coordinator will pass the Rutema __configuration__ giving you access to the context which can contain data like headings and version numbers to use in the report.
|
516
|
-
#
|
517
|
-
#runner_status is a Patir::CommandSequenceStatus containing the status of the last run (so it contains all the Scenario stati for the loaded tests)
|
518
|
-
#
|
519
|
-
#parse_errors is an Array of {:filename,:error} hashes containing the errors encounter by the parser when loading the specifications
|
520
|
-
def report runner_status,parse_errors,configuration
|
521
|
-
|
522
|
-
end
|
523
|
-
end
|
524
518
|
|
525
|
-
#The following configuration keys are used by EmailReporter:
|
526
|
-
#
|
527
|
-
#:server - the smtp server to use
|
528
|
-
#
|
529
|
-
#:port - the port to use (defaults to 25)
|
530
|
-
#
|
531
|
-
#:sender - the sender of the email (defaults to rutema@domain)
|
532
|
-
#
|
533
|
-
#:recipients - an array of strings with the recipients of the report emails
|
534
|
-
#
|
535
|
-
#The :logger key is set by the Coordinator
|
536
|
-
#
|
537
|
-
#Customization keys:
|
538
|
-
#
|
539
|
-
#:subject - the string of this key will be prefixed as a subject for the email
|
540
|
-
class EmailReporter
|
541
|
-
attr_reader :last_message
|
542
|
-
def initialize definition
|
543
|
-
#get the logger
|
544
|
-
@logger=definition[:logger]
|
545
|
-
@logger||=Patir.setup_logger
|
546
|
-
#extract the parameters from the definition
|
547
|
-
#address and port of the smtp server
|
548
|
-
@server=definition[:server]
|
549
|
-
@port=definition[:port]
|
550
|
-
@port||=25
|
551
|
-
#the domain we're coming from
|
552
|
-
@domain=definition[:domain]
|
553
|
-
#construct the mail factory object
|
554
|
-
@mail = MailFactory.new()
|
555
|
-
@mail.from = definition[:sender]
|
556
|
-
@mail.from||="rutema@#{@domain}"
|
557
|
-
@recipients=definition[:recipients]
|
558
|
-
@mail.to=@recipients
|
559
|
-
#customize
|
560
|
-
@subject=definition[:subject]
|
561
|
-
@subject||=""
|
562
|
-
#this is a way to test without sending
|
563
|
-
@dummy=true if definition[:dummy]
|
564
|
-
@logger.info("Reporter '#{self.to_s}' registered")
|
565
|
-
end
|
566
|
-
|
567
|
-
def to_s
|
568
|
-
list=@recipients.join(', ')
|
569
|
-
"EmailReporter - #{@server}:#{@port} from #{@mail.from} to #{list}"
|
570
|
-
end
|
571
|
-
|
572
|
-
def report runner_status,parse_errors,configuration
|
573
|
-
@mail.subject = "#{@subject}"
|
574
|
-
@mail.text = Rutema.text_report(runner_status,parse_errors)
|
575
|
-
begin
|
576
|
-
if @mail.to.empty?
|
577
|
-
@logger.error("No recipients for the report mail")
|
578
|
-
else
|
579
|
-
#
|
580
|
-
#~ if @password
|
581
|
-
#~ #if a password is defined, use cram_md5 authentication
|
582
|
-
#~ else
|
583
|
-
Net::SMTP.start(@server, @port, @domain) {|smtp| smtp.sendmail(@mail.to_s(),@mail.from,mail_to)} unless @dummy
|
584
|
-
#~ end
|
585
|
-
end#recipients empty
|
586
|
-
rescue
|
587
|
-
@logger.error("Sending of email report failed: #{$!}")
|
588
|
-
end
|
589
|
-
@mail.to_s
|
590
|
-
end
|
591
|
-
end
|
592
|
-
private
|
593
|
-
def self.text_report runner_status,parse_errors
|
594
|
-
msg="Parse errors: #{parse_errors.size}"
|
595
|
-
parse_errors.each{|e| msg<<"\n\t#{e[:filename]}"}
|
596
|
-
msg<<"\nCurrent run:\nScenarios: #{runner_status.step_states.size}"
|
597
|
-
msg<<"\n#{runner_status.summary}"
|
598
|
-
return msg
|
599
|
-
end
|
600
519
|
end
|
data/test/test_configuration.rb
CHANGED
@@ -5,7 +5,7 @@ require 'test/unit'
|
|
5
5
|
module TestRutema
|
6
6
|
require 'rutema/system'
|
7
7
|
|
8
|
-
class
|
8
|
+
class TestRutemaXConfigurator<Test::Unit::TestCase
|
9
9
|
def setup
|
10
10
|
@prev_dir=Dir.pwd
|
11
11
|
Dir.chdir(File.dirname(__FILE__))
|
@@ -16,7 +16,7 @@ module TestRutema
|
|
16
16
|
def test_configuration
|
17
17
|
cfg=nil
|
18
18
|
#load the valid configuration
|
19
|
-
assert_nothing_raised() { cfg=Rutema::
|
19
|
+
assert_nothing_raised() { cfg=Rutema::RutemaXConfigurator.new("samples/valid_config.rb").configuration}
|
20
20
|
assert_not_nil(cfg.parser)
|
21
21
|
assert_not_nil(cfg.reporters)
|
22
22
|
assert_equal(1, cfg.reporters.size)
|
@@ -29,7 +29,7 @@ module TestRutema
|
|
29
29
|
assert_not_nil(cfg.context)
|
30
30
|
end
|
31
31
|
def test_specification_paths
|
32
|
-
cfg=Rutema::
|
32
|
+
cfg=Rutema::RutemaXConfigurator.new("samples/valid_config.rb").configuration
|
33
33
|
assert_not_nil(cfg.tests)
|
34
34
|
end
|
35
35
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","lib")
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","ext")
|
3
|
+
require 'test/unit'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rutema/historian'
|
7
|
+
#$DEBUG=true
|
8
|
+
module TestRutema
|
9
|
+
class TestHistorian<Test::Unit::TestCase
|
10
|
+
DB_FILE="test.db"
|
11
|
+
def setup
|
12
|
+
@prev_dir=Dir.pwd
|
13
|
+
Dir.chdir(File.dirname(__FILE__))
|
14
|
+
@configuration=OpenStruct.new
|
15
|
+
@configuration.source=DB_FILE
|
16
|
+
#FileUtils.rm_rf(DB_FILE)
|
17
|
+
end
|
18
|
+
def teardown
|
19
|
+
Dir.chdir(@prev_dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_history
|
23
|
+
assert_nothing_raised() do
|
24
|
+
h=Rutema::Historian.new(@configuration)
|
25
|
+
h.history(:all)
|
26
|
+
h.history("test1")
|
27
|
+
h.history("test5")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","lib")
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","ext")
|
3
|
+
require 'test/unit'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rutema/reporter'
|
7
|
+
require 'rutema/reporter_ar'
|
8
|
+
#$DEBUG=true
|
9
|
+
module TestRutema
|
10
|
+
class MockCommand
|
11
|
+
include Patir::Command
|
12
|
+
def initialize number
|
13
|
+
@number=number
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class TestActiveRecordReporter<Test::Unit::TestCase
|
17
|
+
DB_FILE="test.db"
|
18
|
+
def setup
|
19
|
+
@prev_dir=Dir.pwd
|
20
|
+
Dir.chdir(File.dirname(__FILE__))
|
21
|
+
@parse_errors=[{:filename=>"f.spec",:error=>"error"}]
|
22
|
+
test1=Patir::CommandSequenceStatus.new("test1")
|
23
|
+
test1.sequence_id=1
|
24
|
+
test1.strategy=:attended
|
25
|
+
test2=Patir::CommandSequenceStatus.new("test2")
|
26
|
+
test2.sequence_id=2
|
27
|
+
test2.strategy=:unattended
|
28
|
+
test1.step=MockCommand.new(1)
|
29
|
+
test2.step=MockCommand.new(2)
|
30
|
+
test2.step=MockCommand.new(3)
|
31
|
+
@status=[test1,test2]
|
32
|
+
FileUtils.rm_rf(DB_FILE)
|
33
|
+
end
|
34
|
+
def teardown
|
35
|
+
Dir.chdir(@prev_dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_report
|
39
|
+
spec1=OpenStruct.new(:name=>"test1")
|
40
|
+
spec2=OpenStruct.new(:name=>"test2",:version=>"10")
|
41
|
+
specs={"test1"=>spec1,
|
42
|
+
"test2"=>spec2
|
43
|
+
}
|
44
|
+
definition={:dbfile=>DB_FILE}
|
45
|
+
r=Rutema::ActiveRecordReporter.new(definition)
|
46
|
+
#without configuration
|
47
|
+
assert_nothing_raised() { r.report(specs,@status,@parse_errors,nil) }
|
48
|
+
configuration=OpenStruct.new
|
49
|
+
#without context member
|
50
|
+
assert_nothing_raised() { r.report(specs,@status,@parse_errors,configuration) }
|
51
|
+
#with a nil context
|
52
|
+
configuration.context=nil
|
53
|
+
assert_nothing_raised() { r.report(specs,@status,@parse_errors,configuration) }
|
54
|
+
#with some context
|
55
|
+
configuration.context="context"
|
56
|
+
assert_nothing_raised() { r.report(specs,@status,@parse_errors,configuration) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
class TestEmailReporter<Test::Unit::TestCase
|
60
|
+
def setup
|
61
|
+
@parse_errors=[{:filename=>"f.spec",:error=>"error"}]
|
62
|
+
st=Patir::CommandSequenceStatus.new("test_seq")
|
63
|
+
st.step=MockCommand.new(1)
|
64
|
+
st.step=MockCommand.new(2)
|
65
|
+
st.step=MockCommand.new(3)
|
66
|
+
@status=[st]
|
67
|
+
end
|
68
|
+
def test_new
|
69
|
+
definition={:server=>"localhost",:port=>25,:recipients=>["test"],:sender=>"rutema",:subject=>"test",:dummy=>true}
|
70
|
+
r=Rutema::EmailReporter.new(definition)
|
71
|
+
assert_nothing_raised() { puts r.report(nil,@status,@parse_errors,nil) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/test/test_system.rb
CHANGED
@@ -6,12 +6,6 @@ require 'ostruct'
|
|
6
6
|
#$DEBUG=true
|
7
7
|
module TestRutema
|
8
8
|
require 'rutema/system'
|
9
|
-
class MockCommand
|
10
|
-
include Patir::Command
|
11
|
-
def initialize number
|
12
|
-
@number=number
|
13
|
-
end
|
14
|
-
end
|
15
9
|
class TestBaseXMlParser<Test::Unit::TestCase
|
16
10
|
SAMPLE_SPEC=<<EOT
|
17
11
|
<specification name="sample">
|
@@ -104,18 +98,4 @@ EOT
|
|
104
98
|
end
|
105
99
|
end
|
106
100
|
|
107
|
-
class TestEmailReporter<Test::Unit::TestCase
|
108
|
-
def setup
|
109
|
-
@parse_errors=[{:filename=>"f.spec",:error=>"error"}]
|
110
|
-
@status=Patir::CommandSequenceStatus.new("test_seq")
|
111
|
-
@status.step=MockCommand.new(1)
|
112
|
-
@status.step=MockCommand.new(2)
|
113
|
-
@status.step=MockCommand.new(3)
|
114
|
-
end
|
115
|
-
def test_new
|
116
|
-
definition={:server=>"localhost",:port=>25,:recipients=>["test"],:sender=>"rutema",:subject=>"test",:dummy=>true}
|
117
|
-
r=Rutema::EmailReporter.new(definition)
|
118
|
-
assert_nothing_raised() { puts r.report(@status,@parse_errors,nil) }
|
119
|
-
end
|
120
|
-
end
|
121
101
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.1
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rutema
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "0.
|
7
|
-
date: 2007-05-
|
6
|
+
version: "0.3"
|
7
|
+
date: 2007-05-30 00:00:00 +02:00
|
8
8
|
summary: rutema is a test execution and management framework for heterogeneous testing environments
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -34,11 +34,19 @@ files:
|
|
34
34
|
- README.txt
|
35
35
|
- COPYING.txt
|
36
36
|
- Rakefile
|
37
|
+
- bin/rutemax
|
38
|
+
- bin/rutemah
|
37
39
|
- lib/rutema/configuration.rb
|
38
40
|
- lib/rutema/specification.rb
|
39
41
|
- lib/rutema/system.rb
|
42
|
+
- lib/rutema/reporter.rb
|
43
|
+
- lib/rutema/reporter_ar.rb
|
44
|
+
- lib/rutema/historian.rb
|
45
|
+
- test/test_configuration.rb
|
40
46
|
- test/test_specification.rb
|
41
47
|
- test/test_system.rb
|
48
|
+
- test/test_historian.rb
|
49
|
+
- test/test_reporter.rb
|
42
50
|
- test/samples/check.spec
|
43
51
|
- test/samples/setup.spec
|
44
52
|
- test/samples/teardown.spec
|
@@ -46,16 +54,24 @@ files:
|
|
46
54
|
- test/samples/tests/no_title.spec
|
47
55
|
- test/samples/tests/sample.spec
|
48
56
|
- test/samples/valid_config.rb
|
57
|
+
- test/samples/rutemah_config.rb
|
49
58
|
test_files:
|
50
59
|
- test/test_configuration.rb
|
60
|
+
- test/test_historian.rb
|
61
|
+
- test/test_reporter.rb
|
51
62
|
- test/test_specification.rb
|
52
63
|
- test/test_system.rb
|
53
|
-
rdoc_options:
|
54
|
-
|
55
|
-
|
56
|
-
|
64
|
+
rdoc_options:
|
65
|
+
- --main
|
66
|
+
- README.txt
|
67
|
+
extra_rdoc_files:
|
68
|
+
- History.txt
|
69
|
+
- Manifest.txt
|
70
|
+
- README.txt
|
71
|
+
- COPYING.txt
|
57
72
|
executables:
|
58
73
|
- rutemax
|
74
|
+
- rutemah
|
59
75
|
extensions: []
|
60
76
|
|
61
77
|
requirements: []
|
@@ -68,7 +84,7 @@ dependencies:
|
|
68
84
|
requirements:
|
69
85
|
- - ">="
|
70
86
|
- !ruby/object:Gem::Version
|
71
|
-
version: "0.
|
87
|
+
version: "0.4"
|
72
88
|
version:
|
73
89
|
- !ruby/object:Gem::Dependency
|
74
90
|
name: highline
|
@@ -88,6 +104,24 @@ dependencies:
|
|
88
104
|
- !ruby/object:Gem::Version
|
89
105
|
version: 0.0.0
|
90
106
|
version:
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: activerecord
|
109
|
+
version_requirement:
|
110
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: 0.0.0
|
115
|
+
version:
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
name: ruport
|
118
|
+
version_requirement:
|
119
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 0.0.0
|
124
|
+
version:
|
91
125
|
- !ruby/object:Gem::Dependency
|
92
126
|
name: hoe
|
93
127
|
version_requirement:
|
@@ -95,5 +129,5 @@ dependencies:
|
|
95
129
|
requirements:
|
96
130
|
- - ">="
|
97
131
|
- !ruby/object:Gem::Version
|
98
|
-
version: 1.2.
|
132
|
+
version: 1.2.1
|
99
133
|
version:
|