ruby-plsql-spec 0.1.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/.gitignore +13 -0
- data/Gemfile +11 -0
- data/History.txt +5 -0
- data/INSTALL-Windows.markdown +55 -0
- data/License.txt +20 -0
- data/README.markdown +91 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/plsql-spec +5 -0
- data/examples/source/award_bonus.rb +29 -0
- data/examples/source/betwnstr.rb +19 -0
- data/examples/source/remove_rooms_by_name.rb +45 -0
- data/examples/source/what_is_profiled.rb +207 -0
- data/examples/spec/award_bonus_spec.rb +35 -0
- data/examples/spec/betwnstr_spec.rb +24 -0
- data/examples/spec/database.yml +16 -0
- data/examples/spec/factories/employee_factory.rb +23 -0
- data/examples/spec/helpers/inspect_helpers.rb +17 -0
- data/examples/spec/helpers/oracle_ebs_helpers.rb +32 -0
- data/examples/spec/helpers/time_helpers.rb +5 -0
- data/examples/spec/oracle_ebs_spec.rb +61 -0
- data/examples/spec/remove_rooms_by_name_spec.rb +51 -0
- data/examples/spec/spec_helper.rb +78 -0
- data/examples/spec/what_is_profiled_spec.rb +12 -0
- data/lib/plsql/coverage.rb +262 -0
- data/lib/plsql/coverage/coverage.css +277 -0
- data/lib/plsql/coverage/details.html.erb +35 -0
- data/lib/plsql/coverage/index.html.erb +71 -0
- data/lib/plsql/coverage/jquery.min.js +154 -0
- data/lib/plsql/coverage/jquery.tablesorter.min.js +2 -0
- data/lib/plsql/coverage/proftab.sql +66 -0
- data/lib/plsql/coverage/rcov.js +43 -0
- data/lib/plsql/coverage/table_line.html.erb +15 -0
- data/lib/plsql/spec.rb +5 -0
- data/lib/plsql/spec/cli.rb +81 -0
- data/lib/plsql/spec/templates/database.yml +16 -0
- data/lib/plsql/spec/templates/helpers/inspect_helpers.rb +17 -0
- data/lib/plsql/spec/templates/helpers/time_helpers.rb +5 -0
- data/lib/plsql/spec/templates/spec_helper.rb +78 -0
- data/lib/plsql/spec/version.rb +5 -0
- data/lib/ruby-plsql-spec.rb +1 -0
- data/ruby-plsql-spec.gemspec +113 -0
- data/spec/plsql/coverage_spec.rb +246 -0
- data/spec/plsql/spec/cli_spec.rb +264 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +61 -0
- metadata +177 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# load 'award_bonus' procedure into the database
|
4
|
+
require 'award_bonus'
|
5
|
+
|
6
|
+
describe "Award bonus" do
|
7
|
+
include EmployeeFactory
|
8
|
+
|
9
|
+
[ [1000, 1234.55, 0.10, 1123.46],
|
10
|
+
[nil, 1234.56, 0.10, 123.46],
|
11
|
+
[1000, 1234.54, 0.10, 1123.45]
|
12
|
+
].each do |salary, sales_amt, commission_pct, result|
|
13
|
+
it "should calculate base salary #{salary.inspect} + sales amount #{sales_amt} * commission percentage #{commission_pct} = salary #{result.inspect}" do
|
14
|
+
employee = create_employee(
|
15
|
+
:commission_pct => commission_pct,
|
16
|
+
:salary => salary
|
17
|
+
)
|
18
|
+
plsql.award_bonus(employee[:employee_id], sales_amt)
|
19
|
+
get_employee(employee[:employee_id])[:salary].should == result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise ORA-06510 exception if commission percentage is missing" do
|
24
|
+
salary, sales_amt, commission_pct = 1000, 1234.55, NULL
|
25
|
+
employee = create_employee(
|
26
|
+
:commission_pct => commission_pct,
|
27
|
+
:salary => salary
|
28
|
+
)
|
29
|
+
lambda {
|
30
|
+
plsql.award_bonus(employee[:employee_id], sales_amt)
|
31
|
+
}.should raise_error(/ORA-06510/)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# load 'betwnstr' procedure into the database
|
4
|
+
require "betwnstr"
|
5
|
+
|
6
|
+
describe "Between string" do
|
7
|
+
it "should be correct in normal case" do
|
8
|
+
plsql.betwnstr('abcdefg', 2, 5).should == 'bcde'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be correct with zero start value" do
|
12
|
+
plsql.betwnstr('abcdefg', 0, 5).should == 'abcde'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be correct with way big end value" do
|
16
|
+
plsql.betwnstr('abcdefg', 5, 500).should == 'efg'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be correct with NULL string" do
|
20
|
+
plsql.betwnstr(NULL, 5, 500).should == NULL
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Change default connection username, password and database.
|
2
|
+
# Specify also host and port if not using tnsnames.ora connection.
|
3
|
+
default:
|
4
|
+
username: hr
|
5
|
+
password: hr
|
6
|
+
database: orcl
|
7
|
+
# host: localhost
|
8
|
+
# port: 1521
|
9
|
+
|
10
|
+
# Add other connection if needed.
|
11
|
+
# You can access them with plsql(:other) where :other is connection name specified below.
|
12
|
+
|
13
|
+
# other:
|
14
|
+
# username: scott
|
15
|
+
# password: tiger
|
16
|
+
# database: xe
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EmployeeFactory
|
2
|
+
# Creates new employee with valid field values.
|
3
|
+
# Pass in parameters only field values that you want to override.
|
4
|
+
def create_employee(params)
|
5
|
+
employee = {
|
6
|
+
:employee_id => plsql.employees2_seq.nextval,
|
7
|
+
:last_name => 'Last',
|
8
|
+
:email => 'last@example.com',
|
9
|
+
:hire_date => Date.today,
|
10
|
+
:job_id => plsql.jobs.first[:job_id],
|
11
|
+
:commission_pct => nil,
|
12
|
+
:salary => nil
|
13
|
+
}.merge(params)
|
14
|
+
plsql.employees2.insert employee
|
15
|
+
get_employee employee[:employee_id]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Select employee by primary key
|
19
|
+
def get_employee(employee_id)
|
20
|
+
plsql.employees2.first :employee_id => employee_id
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# As ruby-plsql returns NUMBER values as BigDecimal values in Ruby then
|
2
|
+
# change format how they are by default displayed
|
3
|
+
BigDecimal.class_eval do
|
4
|
+
def inspect
|
5
|
+
to_s('F')
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# As ruby-plsql returns NULL as Ruby nil then change nil.inspect to return 'NULL'
|
10
|
+
NilClass.class_eval do
|
11
|
+
def inspect
|
12
|
+
'NULL'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# NULL looks more like SQL NULL than nil
|
17
|
+
NULL = nil
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# initialize Oracle E-Business Suite session with specified user and responsibility
|
2
|
+
def init_ebs_user(params={})
|
3
|
+
# replace with default user name and responsibility and then it will not be necessary
|
4
|
+
# to specify default value as parameter
|
5
|
+
params = {
|
6
|
+
:user_name => "default user name",
|
7
|
+
:responsibility_name => "default responsibility"
|
8
|
+
}.merge(params)
|
9
|
+
|
10
|
+
row = plsql.select(:first, "SELECT usr.user_name, res.responsibility_name, usr.user_id,
|
11
|
+
urg.responsibility_id, urg.responsibility_application_id resp_appl_id
|
12
|
+
FROM apps.fnd_user_resp_groups urg,
|
13
|
+
applsys.fnd_user usr,
|
14
|
+
fnd_responsibility_vl res
|
15
|
+
WHERE usr.user_name = :user_name
|
16
|
+
AND res.responsibility_name = :responsibility_name
|
17
|
+
AND urg.user_id = usr.user_id
|
18
|
+
AND res.responsibility_id = urg.responsibility_id",
|
19
|
+
params[:user_name], params[:responsibility_name])
|
20
|
+
|
21
|
+
raise ArgumentError, "Wrong user name or responsibility name" unless row
|
22
|
+
|
23
|
+
plsql.fnd_global.apps_initialize(
|
24
|
+
:user_id => row[:user_id],
|
25
|
+
:resp_id => row[:responsibility_id],
|
26
|
+
:resp_appl_id => row[:resp_appl_id]
|
27
|
+
)
|
28
|
+
|
29
|
+
# uncomment if logging to dbms_output is necessary
|
30
|
+
# plsql.dbms_output.put_line("Initialized " + params[:user_name] + " / " + params[:responsibility_name])
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Oracle E-Business Suite" do
|
4
|
+
before(:all) do
|
5
|
+
# @old_connection = plsql.connection
|
6
|
+
# plsql.connect! "APPS", "APPS", "VIS"
|
7
|
+
@user_name = "OPERATIONS"
|
8
|
+
@responsibility_name = "System Administrator"
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
# plsql.connection = @old_connection
|
13
|
+
end
|
14
|
+
|
15
|
+
if plsql.schema_name == 'APPS'
|
16
|
+
|
17
|
+
describe "Session initialization" do
|
18
|
+
it "should initialize session with valid user and responsibility" do
|
19
|
+
lambda {
|
20
|
+
init_ebs_user(:user_name => @user_name, :responsibility_name => @responsibility_name)
|
21
|
+
}.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise error with invalid user" do
|
25
|
+
lambda {
|
26
|
+
init_ebs_user(:user_name => "INVALID", :responsibility_name => @responsibility_name)
|
27
|
+
}.should raise_error(/Wrong user name or responsibility name/)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise error with invalid responsibility" do
|
31
|
+
lambda {
|
32
|
+
init_ebs_user(:user_name => @user_name, :responsibility_name => "INVALID")
|
33
|
+
}.should raise_error(/Wrong user name or responsibility name/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise error with default username and responsibility parameters" do
|
37
|
+
lambda {
|
38
|
+
init_ebs_user
|
39
|
+
}.should raise_error(/Wrong user name or responsibility name/)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "Session information" do
|
45
|
+
before(:all) do
|
46
|
+
init_ebs_user(:user_name => @user_name, :responsibility_name => @responsibility_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return user name" do
|
50
|
+
plsql.fnd_global.user_name.should == @user_name
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return responsibility name" do
|
54
|
+
plsql.fnd_global.resp_name.should == @responsibility_name
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'remove_rooms_by_name'
|
4
|
+
|
5
|
+
describe "Remove rooms by name" do
|
6
|
+
before(:all) do
|
7
|
+
plsql.rooms.insert_values(
|
8
|
+
[:room_key, :name],
|
9
|
+
[1, 'Dining Room'],
|
10
|
+
[2, 'Living Room'],
|
11
|
+
[3, 'Office'],
|
12
|
+
[4, 'Bathroom'],
|
13
|
+
[5, 'Bedroom']
|
14
|
+
)
|
15
|
+
plsql.room_contents.insert_values(
|
16
|
+
[:contents_key, :room_key, :name],
|
17
|
+
[1, 1, 'Table'],
|
18
|
+
[2, 1, 'Hutch'],
|
19
|
+
[3, 1, 'Chair'],
|
20
|
+
[4, 2, 'Sofa'],
|
21
|
+
[5, 2, 'Lamp'],
|
22
|
+
[6, 3, 'Desk'],
|
23
|
+
[7, 3, 'Chair'],
|
24
|
+
[8, 3, 'Computer'],
|
25
|
+
[9, 3, 'Whiteboard']
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should remove a room without furniture" do
|
30
|
+
rooms_without_b = plsql.rooms.all("WHERE name NOT LIKE 'B%'")
|
31
|
+
plsql.remove_rooms_by_name('B%')
|
32
|
+
plsql.rooms.all.should == rooms_without_b
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should not remove a room with furniture" do
|
36
|
+
lambda {
|
37
|
+
lambda {
|
38
|
+
plsql.remove_rooms_by_name('Living Room')
|
39
|
+
}.should raise_error(/ORA-02292/)
|
40
|
+
}.should_not change { plsql.rooms.all }
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should raise exception when NULL value passed" do
|
44
|
+
lambda {
|
45
|
+
lambda {
|
46
|
+
plsql.remove_rooms_by_name(NULL)
|
47
|
+
}.should raise_error(/program error/)
|
48
|
+
}.should_not change { plsql.rooms.all }
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "ruby-plsql-spec"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
# create all connections specified in database.yml file
|
6
|
+
database_config_file = File.expand_path('../database.yml', __FILE__)
|
7
|
+
database_config = YAML.load(File.read(database_config_file))
|
8
|
+
database_config = {} unless database_config.is_a?(Hash)
|
9
|
+
database_connections = database_config.keys.map{|k| k.to_sym}
|
10
|
+
|
11
|
+
database_config.each do |name, params|
|
12
|
+
# change all keys to symbols
|
13
|
+
name = name.to_sym
|
14
|
+
symbol_params = Hash[*params.map{|k,v| [k.to_sym, v]}.flatten]
|
15
|
+
|
16
|
+
plsql(name).connect! symbol_params
|
17
|
+
|
18
|
+
# Set autocommit to false so that automatic commits after each statement are _not_ performed
|
19
|
+
plsql(name).connection.autocommit = false
|
20
|
+
# reduce network traffic in case of large resultsets
|
21
|
+
plsql(name).connection.prefetch_rows = 100
|
22
|
+
# log DBMS_OUTPUT to standard output
|
23
|
+
if ENV['PLSQL_DBMS_OUTPUT']
|
24
|
+
plsql(name).dbms_output_stream = STDOUT
|
25
|
+
end
|
26
|
+
|
27
|
+
# start code coverage collection
|
28
|
+
if ENV['PLSQL_COVERAGE']
|
29
|
+
PLSQL::Coverage.start(name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Do logoff when exiting to ensure that session temporary tables
|
34
|
+
# (used when calling procedures with table types defined in packages)
|
35
|
+
at_exit do
|
36
|
+
database_connections.each do |name|
|
37
|
+
if ENV['PLSQL_COVERAGE']
|
38
|
+
PLSQL::Coverage.stop(name)
|
39
|
+
coverage_directory = name == :default ? ENV['PLSQL_COVERAGE'] : "#{ENV['PLSQL_COVERAGE']}/#{name}"
|
40
|
+
options = {:directory => coverage_directory}
|
41
|
+
options[:ignore_schemas] = ENV['PLSQL_COVERAGE_IGNORE_SCHEMAS'].split(',') if ENV['PLSQL_COVERAGE_IGNORE_SCHEMAS']
|
42
|
+
options[:like] = ENV['PLSQL_COVERAGE_LIKE'].split(',') if ENV['PLSQL_COVERAGE_LIKE']
|
43
|
+
PLSQL::Coverage.report name, options
|
44
|
+
PLSQL::Coverage.cleanup name
|
45
|
+
end
|
46
|
+
plsql(name).logoff
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Spec::Runner.configure do |config|
|
51
|
+
config.before(:each) do
|
52
|
+
database_connections.each do |name|
|
53
|
+
plsql(name).savepoint "before_each"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
config.after(:each) do
|
57
|
+
# Always perform rollback to savepoint after each test
|
58
|
+
database_connections.each do |name|
|
59
|
+
plsql(name).rollback_to "before_each"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
config.after(:all) do
|
63
|
+
# Always perform rollback after each describe block
|
64
|
+
database_connections.each do |name|
|
65
|
+
plsql(name).rollback
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# require all helper methods which are located in any helpers subdirectories
|
71
|
+
Dir[File.dirname(__FILE__) + '/**/helpers/*.rb'].each {|f| require f}
|
72
|
+
|
73
|
+
# require all factory modules which are located in any factories subdirectories
|
74
|
+
Dir[File.dirname(__FILE__) + '/**/factories/*.rb'].each {|f| require f}
|
75
|
+
|
76
|
+
# If necessary add source directory to load path where PL/SQL procedures are defined.
|
77
|
+
# It is not required if PL/SQL procedures are already loaded in test database in some other way.
|
78
|
+
$:.push File.dirname(__FILE__) + '/../source'
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module PLSQL
|
2
|
+
class Coverage
|
3
|
+
@@coverages = {}
|
4
|
+
|
5
|
+
attr_accessor :directory
|
6
|
+
|
7
|
+
# used in tests to reset coverages cache
|
8
|
+
def self.reset_cache
|
9
|
+
@@coverages = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.start(connection_alias = nil)
|
13
|
+
find_or_new(connection_alias).start
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(connection_alias)
|
17
|
+
@connection_alias = connection_alias
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
# ignore repeated invocation
|
22
|
+
return false if @started
|
23
|
+
create_profiler_tables
|
24
|
+
result = plsql(@connection_alias).dbms_profiler.start_profiler(
|
25
|
+
:run_comment => "ruby-plsql-spec #{Time.now.xmlschema}",
|
26
|
+
:run_number => nil
|
27
|
+
)
|
28
|
+
@run_number = result[1][:run_number]
|
29
|
+
@coverages = nil
|
30
|
+
@started = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.stop(connection_alias = nil)
|
34
|
+
find_or_new(connection_alias).stop
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
# ignore repeated invocation
|
39
|
+
return false unless @started
|
40
|
+
plsql(@connection_alias).dbms_profiler.stop_profiler
|
41
|
+
@started = false
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.cleanup(connection_alias = nil)
|
46
|
+
find(connection_alias).cleanup
|
47
|
+
end
|
48
|
+
|
49
|
+
def cleanup
|
50
|
+
return false if @started
|
51
|
+
drop_or_delete_profiler_tables
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.find(connection_alias = nil)
|
56
|
+
connection_alias ||= :default
|
57
|
+
@@coverages[connection_alias]
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.report(connection_alias = nil, options = {})
|
61
|
+
# if first parameter is Hash then consider it as options and use default connection
|
62
|
+
if connection_alias.is_a?(Hash)
|
63
|
+
options = connection_alias
|
64
|
+
connection_alias = nil
|
65
|
+
end
|
66
|
+
find(connection_alias).report(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def report(options={})
|
70
|
+
# prevent repeated invocation after coverage is reported
|
71
|
+
# return if @coverages
|
72
|
+
|
73
|
+
@directory = options.delete(:directory)
|
74
|
+
coverage_data(options)
|
75
|
+
create_static_files
|
76
|
+
read_templates
|
77
|
+
|
78
|
+
# Loop through each database object, evaluating it along with the template
|
79
|
+
@coverage_data.keys.sort.each do |schema|
|
80
|
+
@coverage_data[schema].keys.sort.each do |object|
|
81
|
+
details_report(schema, object)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
index_report
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def coverage_data(options={})
|
90
|
+
quoted_ignore_schemas = if options[:ignore_schemas]
|
91
|
+
options[:ignore_schemas].map{|schema| quote_condition_string(schema)}
|
92
|
+
else
|
93
|
+
%w('SYS')
|
94
|
+
end
|
95
|
+
quoted_ignore_schemas << "'<anonymous>'"
|
96
|
+
like_condition = if options[:like]
|
97
|
+
'AND ((' << Array(options[:like]).map do |like|
|
98
|
+
like_schema, like_object = like.split('.')
|
99
|
+
condition = "u.unit_owner LIKE #{quote_condition_string(like_schema)}"
|
100
|
+
condition << " AND u.unit_name LIKE #{quote_condition_string(like_object)}" if like_object
|
101
|
+
end.join(') OR (') << '))'
|
102
|
+
else
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
data = {}
|
106
|
+
rows = plsql(@connection_alias).select_all <<-EOS
|
107
|
+
SELECT u.unit_owner, u.unit_name, d.line# line_number, d.total_occur
|
108
|
+
FROM plsql_profiler_units u, plsql_profiler_data d
|
109
|
+
WHERE u.runid = #{@run_number}
|
110
|
+
AND u.unit_owner NOT IN (#{quoted_ignore_schemas.join(',')})
|
111
|
+
AND u.runid = d.runid
|
112
|
+
AND u.unit_number = d.unit_number
|
113
|
+
#{like_condition}
|
114
|
+
ORDER BY u.unit_owner, u.unit_name, d.line#
|
115
|
+
EOS
|
116
|
+
rows.each do |row|
|
117
|
+
unit_owner, unit_name, line_number, total_occur = row
|
118
|
+
data[unit_owner] ||= {}
|
119
|
+
data[unit_owner][unit_name] ||= {}
|
120
|
+
data[unit_owner][unit_name][line_number] = total_occur
|
121
|
+
end
|
122
|
+
@coverage_data = data
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def self.find_or_new(connection_alias) #:nodoc:
|
128
|
+
connection_alias ||= :default
|
129
|
+
if @@coverages[connection_alias]
|
130
|
+
@@coverages[connection_alias]
|
131
|
+
else
|
132
|
+
@@coverages[connection_alias] = self.new(connection_alias)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_profiler_tables
|
137
|
+
unless PLSQL::Table.find(plsql(@connection_alias), 'plsql_profiler_data')
|
138
|
+
proftab_file = File.expand_path('../coverage/proftab.sql', __FILE__)
|
139
|
+
File.read(proftab_file).split(";\n").each do |sql|
|
140
|
+
if sql =~ /^drop/i
|
141
|
+
plsql(@connection_alias).execute sql rescue nil
|
142
|
+
elsif sql =~ /^(create|comment)/i
|
143
|
+
plsql(@connection_alias).execute sql
|
144
|
+
end
|
145
|
+
end
|
146
|
+
@created_profiler_tables = true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def drop_or_delete_profiler_tables
|
151
|
+
if @created_profiler_tables
|
152
|
+
%w(plsql_profiler_data plsql_profiler_units plsql_profiler_runs).each do |table|
|
153
|
+
plsql(@connection_alias).execute "DROP TABLE #{table} CASCADE CONSTRAINTS"
|
154
|
+
end
|
155
|
+
plsql(@connection_alias).execute "DROP SEQUENCE plsql_profiler_runnumber"
|
156
|
+
else @run_number
|
157
|
+
plsql(@connection_alias).execute <<-SQL, @run_number
|
158
|
+
DECLARE
|
159
|
+
PRAGMA AUTONOMOUS_TRANSACTION;
|
160
|
+
v_runid BINARY_INTEGER := :runid;
|
161
|
+
BEGIN
|
162
|
+
DELETE FROM plsql_profiler_data WHERE runid = v_runid;
|
163
|
+
DELETE FROM plsql_profiler_units WHERE runid = v_runid;
|
164
|
+
DELETE FROM plsql_profiler_runs WHERE runid = v_runid;
|
165
|
+
COMMIT;
|
166
|
+
END;
|
167
|
+
SQL
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def quote_condition_string(string)
|
172
|
+
"'#{string.to_s.upcase.gsub(/[^\w\d\$\%\_]/,'')}'"
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_static_files
|
176
|
+
FileUtils.mkdir_p("#{@directory}")
|
177
|
+
%w(coverage.css jquery.min.js jquery.tablesorter.min.js rcov.js).each do |file|
|
178
|
+
FileUtils.cp File.expand_path("../coverage/#{file}", __FILE__), "#{@directory}/#{file}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def read_templates
|
183
|
+
%w(details table_line index).each do |template|
|
184
|
+
template_erb = File.read(File.expand_path("../coverage/#{template}.html.erb", __FILE__))
|
185
|
+
instance_variable_set("@#{template}_template", ERB.new(template_erb, nil, '><'))
|
186
|
+
end
|
187
|
+
|
188
|
+
@table_lines = []
|
189
|
+
@total_lines = @analyzed_lines = @executed_lines = 0
|
190
|
+
end
|
191
|
+
|
192
|
+
def details_report(schema, object)
|
193
|
+
source = plsql(@connection_alias).select_all <<-EOS, schema, object
|
194
|
+
SELECT s.line, s.text
|
195
|
+
FROM all_source s
|
196
|
+
WHERE s.owner = :owner
|
197
|
+
AND s.name = :name
|
198
|
+
AND s.type NOT IN ('PACKAGE')
|
199
|
+
ORDER BY s.line
|
200
|
+
EOS
|
201
|
+
coverage = (@coverage_data[schema]||{})[object]||{}
|
202
|
+
|
203
|
+
total_lines = source.length
|
204
|
+
# return if no access to source of database object
|
205
|
+
# or if package body is wrapped
|
206
|
+
return if total_lines == 0 || source[0][1] =~ /^\s*PACKAGE BODY .* WRAPPED/i
|
207
|
+
|
208
|
+
# sometimes first PROCEDURE or FUNCTION line is reported as not executed, force ignoring it
|
209
|
+
source.each do |line, text|
|
210
|
+
if text =~ /^\s*(PROCEDURE|FUNCTION)/ && coverage[line] == 0
|
211
|
+
coverage.delete(line)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
@total_lines += total_lines
|
216
|
+
analyzed_lines = executed_lines = 0
|
217
|
+
coverage.each do |line, value|
|
218
|
+
analyzed_lines += 1
|
219
|
+
executed_lines += 1 if value > 0
|
220
|
+
end
|
221
|
+
@analyzed_lines += analyzed_lines
|
222
|
+
@executed_lines += executed_lines
|
223
|
+
total_coverage = (total_lines - analyzed_lines + executed_lines).to_f / total_lines * 100
|
224
|
+
code_coverage = analyzed_lines > 0 ? executed_lines.to_f / analyzed_lines * 100 : 0
|
225
|
+
|
226
|
+
file_name = "#{schema}-#{object}.html"
|
227
|
+
object_name = "#{schema}.#{object}"
|
228
|
+
|
229
|
+
table_line_html = @table_line_template.result binding
|
230
|
+
@table_lines << table_line_html
|
231
|
+
html = @details_template.result binding
|
232
|
+
|
233
|
+
File.open("#{@directory}/#{file_name}", "w") do |file|
|
234
|
+
file.write html
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def index_report
|
239
|
+
schemas = @coverage_data.keys.sort
|
240
|
+
table_lines_html = @table_lines.join("\n")
|
241
|
+
|
242
|
+
total_lines, analyzed_lines, executed_lines = @total_lines, @analyzed_lines, @executed_lines
|
243
|
+
# return if no access to source of database objects
|
244
|
+
return if total_lines == 0
|
245
|
+
|
246
|
+
total_coverage = (total_lines - analyzed_lines + executed_lines).to_f / total_lines * 100
|
247
|
+
code_coverage = analyzed_lines > 0 ? executed_lines.to_f / analyzed_lines * 100 : 0
|
248
|
+
|
249
|
+
schema = file_name = nil
|
250
|
+
object_name = 'TOTAL'
|
251
|
+
|
252
|
+
table_footer_html = @table_line_template.result binding
|
253
|
+
|
254
|
+
html = @index_template.result binding
|
255
|
+
|
256
|
+
File.open("#{@directory}/index.html", "w") do |file|
|
257
|
+
file.write html
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|