theotokos 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.travis.yml +6 -0
  2. data/Gemfile +5 -0
  3. data/LICENSE +20 -0
  4. data/README.md +16 -0
  5. data/Rakefile +30 -0
  6. data/bin/theotokos +20 -0
  7. data/html/Chart.min.js +10 -0
  8. data/html/bootstrap-collapse.js +167 -0
  9. data/html/bootstrap.min.css +9 -0
  10. data/html/jquery.min.js +2 -0
  11. data/lib/assertion/test_assertion.rb +44 -0
  12. data/lib/config/app_config_params.rb +40 -0
  13. data/lib/config/app_logger.rb +48 -0
  14. data/lib/engine/execution_initializer.rb +115 -0
  15. data/lib/engine/executor.rb +128 -0
  16. data/lib/engine/parser.rb +39 -0
  17. data/lib/engine/soap_executor.rb +101 -0
  18. data/lib/helper/app_helper.rb +99 -0
  19. data/lib/helper/hook_helper.rb +49 -0
  20. data/lib/helper/html_helper.rb +633 -0
  21. data/lib/model/execution.rb +16 -0
  22. data/lib/model/test.rb +79 -0
  23. data/lib/model/test_app_result.rb +91 -0
  24. data/lib/model/test_result.rb +22 -0
  25. data/lib/model/test_status.rb +68 -0
  26. data/lib/model/test_suite.rb +80 -0
  27. data/lib/model/test_suite_result.rb +54 -0
  28. data/lib/model/text_operator.rb +34 -0
  29. data/lib/model/ws_config.rb +48 -0
  30. data/lib/net/soap_net.rb +58 -0
  31. data/lib/report/chart_factory.rb +68 -0
  32. data/lib/report/console.rb +94 -0
  33. data/lib/report/default_locale +64 -0
  34. data/lib/report/html.rb +55 -0
  35. data/lib/report/json.rb +19 -0
  36. data/lib/report/reporter.rb +76 -0
  37. data/lib/requires.rb +43 -0
  38. data/lib/theotokos.rb +56 -0
  39. data/lib/version.rb +8 -0
  40. data/test/app-cfg.yml +10 -0
  41. data/test/config/ws-config.yml +8 -0
  42. data/test/unit/assertion/test_test_assertion.rb +72 -0
  43. data/test/unit/config/test_app_config_params.rb +35 -0
  44. data/test/unit/engine/test_execution_initializer.rb +22 -0
  45. data/test/unit/engine/test_parser.rb +35 -0
  46. data/test/unit/model/test_execution.rb +29 -0
  47. data/test/unit/model/test_test.rb +42 -0
  48. data/test/unit/model/test_test_app_result.rb +115 -0
  49. data/test/unit/model/test_test_status.rb +66 -0
  50. data/test/unit/model/test_test_suite.rb +92 -0
  51. data/test/unit/model/test_test_suite_result.rb +77 -0
  52. data/test/unit/model/test_text_operator.rb +40 -0
  53. data/test/unit/model/test_ws_config.rb +31 -0
  54. data/test/unit/report/test_json.rb +42 -0
  55. data/test/ws-test-models/do_something.yml +9 -0
  56. data/test/ws-test-models/project1/look_for_stuff.yml +4 -0
  57. data/theotokos.gemspec +25 -0
  58. metadata +155 -0
@@ -0,0 +1,48 @@
1
+ module Theotokos
2
+ module Configuration
3
+
4
+ class AppLogger
5
+
6
+ @@appenders = []
7
+
8
+ def self.add_stdout_logger
9
+ Logging.appenders.stdout(
10
+ :level => ENV['logger.stdout.level'].to_sym,
11
+ :layout => Logging::Layouts::Pattern.new(:date_pattern => ENV['logger.stdout.layout.date_pattern'], :pattern => ENV['logger.stdout.layout.pattern'])
12
+ )
13
+
14
+ @@appenders << 'stdout' unless @@appenders.include? 'stdout'
15
+ end
16
+
17
+ def self.add_rolling_file_logger
18
+ dir = File.dirname ENV['logger.rolling_file.file']
19
+ FileUtils.mkdir_p dir unless File.exist? dir
20
+
21
+ file = ENV['logger.rolling_file.file'].dup
22
+ file << ".#{Time.now.strftime('%Y-%m-%d')}" unless ENV['logger.rolling_file.file.append_date'] == 'false'
23
+
24
+ Logging.appenders.rolling_file(
25
+ file, :level => ENV['logger.rolling_file.level'].to_sym,
26
+ :layout => Logging::Layouts::Pattern.new(:date_pattern => ENV['logger.rolling_file.date_pattern'],
27
+ :pattern => ENV['logger.rolling_file.pattern'])
28
+ )
29
+
30
+ @@appenders << file unless @@appenders.include? file
31
+ end
32
+
33
+ def self.create_logger(source = nil)
34
+ if @@appenders.empty?
35
+ puts 'WARNING! There is no appender specified.'
36
+ return nil
37
+ end
38
+
39
+ logger = Logging.logger[source ||= self]
40
+ logger.add_appenders @@appenders
41
+
42
+ return logger
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,115 @@
1
+ module Theotokos
2
+ module Engine
3
+
4
+ class ExecutionInitializer
5
+
6
+ def initialize
7
+ yield self if block_given?
8
+ end
9
+
10
+ def init_executors
11
+ raise Exception, 'Execution command params must not be empty.' unless @command
12
+ puts "\n >>> Tags: #{@command.tags.join(', ')}\n\n" unless (@command.tags.nil? || @command.tags.empty?)
13
+ suites = []
14
+ console_report = Reporter.create_reporter(:console) if @command.report_formats.include? :console
15
+
16
+ ExecutionInitializer._before_app
17
+ Helper.load_support_files
18
+
19
+ @command.test_files.each do |file|
20
+ model = nil
21
+ begin
22
+ model = Parser.yaml_to_test_suite file
23
+ rescue Exception => ex
24
+ suites << ExecutionInitializer.error_test_suite_result(ex, file)
25
+ puts "WARN: Error parsing model '#{file}': #{ex}"
26
+ next
27
+ end
28
+
29
+ model.source = file
30
+
31
+ suite = SoapExecutor.new do |exe|
32
+ exe.test_suite = model
33
+ exe.test_index = @command.test_index
34
+ exe.ws_config = @ws_config
35
+ exe.tags_input = @command.tags
36
+ exe.console_report = console_report
37
+ end.execute
38
+
39
+ next if suite.nil?
40
+
41
+ suite.calculate_totals
42
+ console_report.print suite unless console_report.nil?
43
+ suites << suite
44
+ puts
45
+ end
46
+
47
+ @test_app_result = TestAppResult.new do |t|
48
+ t.suites = suites
49
+ t.calculate_totals
50
+ end
51
+
52
+ ExecutionInitializer._after_app @test_app_result
53
+ console_report.print @test_app_result unless console_report.nil?
54
+ end
55
+
56
+ def self.load_test_models(path)
57
+ ((path.nil?) ? ExecutionInitializer.load_all_test_models : ExecutionInitializer.load_test_models_by_path(path))
58
+ end
59
+
60
+ def self.load_test_models_by_path(model_path)
61
+ path = model_path.dup
62
+
63
+ unless path.end_with? '.yml'
64
+ path << '/' unless path.end_with? '/'
65
+ path << '**/*.yml'
66
+ end
67
+
68
+ Dir.glob(path).map {|file| file }
69
+ end
70
+
71
+ def self.load_all_test_models
72
+ ExecutionInitializer.load_test_models_by_path ENV['ws.test.models.path']
73
+ end
74
+
75
+ def self.error_test_suite_result(ex, file)
76
+ TestSuiteResult.new do |r|
77
+ r.model = TestSuite.new do |s|
78
+ s.source = file
79
+ s.wsdl = '???'
80
+ s.service = '???'
81
+ end
82
+
83
+ r.test_results = [
84
+ TestResult.new do |test|
85
+ test.name = '1'
86
+ test.status = TestStatus.new :test_file_status => false, :test_text_status => { :error => true }
87
+ test.error = { :message => ex.to_s, :backtrace => ex.backtrace }
88
+ end
89
+ ]
90
+ end
91
+ end
92
+
93
+ def self._before_app
94
+ return if HOOKS[:before_app].nil?
95
+
96
+ HOOKS[:before_app].each do |block|
97
+ block.call
98
+ end
99
+ end
100
+
101
+ def self._after_app(result)
102
+ return if HOOKS[:after_app].nil?
103
+
104
+ HOOKS[:after_app].each do |block|
105
+ block.call result
106
+ end
107
+ end
108
+
109
+ attr_accessor :command, :ws_config
110
+ attr_reader :test_app_result
111
+
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,128 @@
1
+ module Theotokos
2
+ module Engine
3
+
4
+ class Executor
5
+
6
+ attr_accessor :test_suite, :test_index, :ws_config, :tags_input, :console_report
7
+
8
+ protected
9
+ def save_web_service_result(data)
10
+ Dir.mkdir 'tmp' unless File.exist? 'tmp'
11
+ file_name = "tmp/#{@test_suite.name}_#{@count}.xml"
12
+ @logger.info "Web service response saved to #{file_name}"
13
+
14
+ File.open(file_name, 'w') {|file| file.write data }
15
+ file_name
16
+ end
17
+
18
+ def should_execute?
19
+ return true if @tags_input.nil?
20
+
21
+ @tags_input.each do |tag|
22
+ return true if @test_suite.has_tag? tag
23
+ end
24
+
25
+ false
26
+ end
27
+
28
+ def should_execute_test?(test)
29
+ return true if @tags_input.nil?
30
+
31
+ @tags_input.each do |tag|
32
+ return true if test.has_tag? tag
33
+ end
34
+
35
+ false
36
+ end
37
+
38
+ def validate_test_execution(expected_output, outcoming_file)
39
+ return TestStatus.new :error => false if expected_output.nil?
40
+
41
+ begin
42
+ res = {}
43
+
44
+ if expected_output['file']
45
+ file = if expected_output['file'].start_with?(ENV['ws.test.output.files.path'])
46
+ expected_output['file']
47
+ else
48
+ "#{ENV['ws.test.output.files.path']}/#{expected_output['file']}"
49
+ end
50
+ res[:file] = TestAssertion.compare_file file, outcoming_file
51
+ end
52
+
53
+ if expected_output['text']
54
+ res[:text] = {}
55
+ assertions = _get_assertions expected_output['text']
56
+
57
+ assertions.each do |assertion|
58
+ res[:text][assertion] = TestAssertion.compare_text expected_output['text'][assertion.to_s], File.read(outcoming_file), assertion
59
+ end
60
+ end
61
+
62
+ return TestStatus.new :test_file_status => res[:file], :test_text_status => res[:text]
63
+ rescue Exception => ex
64
+ stack = ex.backtrace.join "\n"
65
+ @logger.warn "Test validation has failed for test ##{@count}: #{ex.to_s}\n#{stack}"
66
+
67
+ return ex
68
+ end
69
+ end
70
+
71
+ def before_suite(test_suite)
72
+ @logger.debug 'Playing Before Suite'
73
+ return if HOOKS[:before_suite].nil?
74
+
75
+ HOOKS[:before_suite].each do |block|
76
+ block.call test_suite
77
+ end
78
+ end
79
+
80
+ def after_suite(test_suite, test_suite_result)
81
+ @logger.debug 'Playing After Suite'
82
+ return if HOOKS[:after_suite].nil?
83
+
84
+ test_suite_result.calculate_totals
85
+ HOOKS[:after_suite].each do |block|
86
+ block.call test_suite, test_suite_result
87
+ end
88
+ end
89
+
90
+ def before_test(test)
91
+ @logger.debug 'Playing Before Test'
92
+ return if HOOKS[:before_test].nil?
93
+
94
+ HOOKS[:before_test].each do |block|
95
+ block.call test
96
+ end
97
+ end
98
+
99
+ def after_test(test, test_result)
100
+ @logger.debug 'Playing After Test'
101
+ return if HOOKS[:after_test].nil?
102
+
103
+ HOOKS[:after_test].each do |block|
104
+ block.call test, test_result
105
+ end
106
+ end
107
+
108
+ private
109
+ def _get_assertions(hash)
110
+ assertions = []
111
+ ['equals', 'contains', 'not_contains', 'regex'].each do |assertion|
112
+ next if hash[assertion].nil?
113
+
114
+ assertions << case assertion
115
+ when 'equals' then :equals
116
+ when 'contains' then :contains
117
+ when 'not_contains' then :not_contains
118
+ when 'regex' then :regex
119
+ end
120
+ end
121
+
122
+ assertions
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,39 @@
1
+ module Theotokos
2
+ module Engine
3
+
4
+ class Parser
5
+
6
+ def self.yaml_to_test_suite(source)
7
+ Parser.hash_to_test_suite Parser.yaml_to_hash(source)
8
+ end
9
+
10
+ def self.yaml_to_hash(source)
11
+ raise Exception, "File #{source} does not exist." unless File.exist? source
12
+ YAML.load_file source
13
+ end
14
+
15
+ def self.hash_to_test_suite(hash)
16
+ Model::TestSuite.new do |suite|
17
+ suite.wsdl = hash['wsdl']
18
+ suite.service = hash['service']
19
+ suite.description = hash['description']
20
+ suite.tags = hash['tags']
21
+
22
+ suite.tests = hash['tests'].map do |te|
23
+ Model::Test.new do |t|
24
+ t.name = te['name']
25
+ t.description = te['description']
26
+ t.input = te['input']
27
+ t.output = te['output']
28
+ t.tags = te['tags']
29
+ t.ws_security = te['ws-security']
30
+ t.error_expected = te['error_expected']
31
+ end
32
+ end unless hash['tests'].nil?
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,101 @@
1
+ module Theotokos
2
+ module Engine
3
+
4
+ class SoapExecutor < Executor
5
+
6
+ def initialize
7
+ @count = 0
8
+ @logger = AppLogger.create_logger self
9
+ yield self if block_given?
10
+ end
11
+
12
+ def execute
13
+ return unless self.should_execute?
14
+ @logger.info "Execute Soap Test '#{@test_suite.source}'"
15
+ @logger.info "WSDL: #{@test_suite.wsdl}"
16
+ @logger.info "Service: #{@test_suite.service}"
17
+
18
+ self.before_suite @test_suite
19
+ test_suite_result = _execute_test_suite
20
+ self.after_suite @test_suite, test_suite_result
21
+
22
+ test_suite_result
23
+ end
24
+
25
+ private
26
+ def _execute_test_suite
27
+ res = TestSuiteResult.new do |r|
28
+ r.model = @test_suite
29
+ r.test_results = _execute
30
+ end
31
+
32
+ res
33
+ end
34
+
35
+ def _execute
36
+ results = []
37
+ @test_suite.tests.each do |test|
38
+ @count += 1
39
+ next if !@test_index.nil? && @test_index != @count
40
+ next unless self.should_execute_test? test
41
+
42
+ result = {}
43
+ self.before_test test
44
+ test_result = TestResult.new do |t|
45
+ t.name = ((test.name.nil?) ? @count : test.name)
46
+ t.description = test.description
47
+ t.error_expected = test.error_expected
48
+ t.tags = test.tags
49
+ t.skip = test.skip
50
+ end
51
+
52
+ test_result.test_expectation = test.output
53
+ if test.skip
54
+ @logger.warn "Skiping test '#{test.name}'"
55
+ results << test_result
56
+ next
57
+ end
58
+
59
+ res = SoapNet.send_request :wsdl => @test_suite.wsdl, :ws_config => @ws_config,
60
+ :ws_security => test.ws_security, :service => @test_suite.service, :params => test.input
61
+
62
+ if res[:success] == false && !test_result.error_expected
63
+ test_result.error = { :message => 'Send request failure', :backtrace => res[:xml] }
64
+ test_result.status = TestStatus.new :error => true
65
+ results << test_result
66
+
67
+ @console_report.print test_result unless @console_report.nil?
68
+ self.after_test test, test_result
69
+ next
70
+ elsif res[:success] == true && test_result.error_expected
71
+ test_result.error = { :message => 'It was supposed to get an exception but nothing was caught.' }
72
+ test_result.status = TestStatus.new :error => true
73
+ results << test_result
74
+
75
+ @console_report.print test_result unless @console_report.nil?
76
+ self.after_test test, test_result
77
+ next
78
+ end
79
+
80
+ test_result.test_actual = save_web_service_result res[:xml]
81
+ test_res = validate_test_execution test.output, test_result.test_actual
82
+
83
+ if test_res.instance_of? Theotokos::Model::TestStatus
84
+ test_result.status = test_res
85
+ else
86
+ test_result.error = { :message => test_res.to_s, :backtrace => test_res.backtrace }
87
+ test_result.status = TestStatus.new :error => true
88
+ end
89
+
90
+ results << test_result
91
+ @console_report.print test_result unless @console_report.nil?
92
+ self.after_test test, test_result
93
+ end
94
+
95
+ results
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,99 @@
1
+ module Theotokos
2
+ module Helper
3
+
4
+ def parse_cmd_options
5
+ command = Execution.new
6
+
7
+ opts = OptionParser.new do |opts|
8
+ opts.banner = "Theotokos: a tool for web service testing"
9
+ opts.define_head "Usage: theotokos [path:test_index] [options]"
10
+ opts.separator ""
11
+ opts.separator "Examples:"
12
+ opts.separator " theotokos"
13
+ opts.separator " theotokos resources/ws-test-model/project_xpto"
14
+ opts.separator " theotokos resources/ws-test-model/project_xpto/web_service_test.yml"
15
+ opts.separator " theotokos resources/ws-test-model/project_xpto/web_service_test.yml:1"
16
+ opts.separator " theotokos -r console html json -t foo"
17
+ opts.separator ""
18
+ opts.separator "Options:"
19
+
20
+ opts.on('-t', "--tags t1,t2,t3", Array, 'Specify test tags') do |tags|
21
+ command.tags = tags
22
+ end
23
+
24
+ opts.on("-r", "--report-formats r1,r2,r3", Array, "Output test report formats. Must be one of [console, html, json]") do |formats|
25
+ params = []
26
+ formats.each do |f|
27
+ raise "Supported report formats are: [console, html, json]. Found '#{f}' in [#{formats.join(', ')}]" unless ['console', 'html', 'json'].include? f
28
+ params << f.to_sym
29
+ end
30
+ command.report_formats = params
31
+ end
32
+
33
+ opts.on("-i", "--internationalization [OPT]", String, "Sets custom locale. Example: en, pt etc.") do |internationalization|
34
+ command.internationalization = internationalization
35
+ end
36
+
37
+ opts.on_tail("-h", "--help", "Show this message") do
38
+ puts opts
39
+ exit
40
+ end
41
+
42
+ opts.on_tail("-v", "--version", "Show version") do
43
+ puts "theotokos #{Theotokos::VERSION}"
44
+ exit
45
+ end
46
+ end
47
+
48
+ begin
49
+ opts.parse!
50
+ test_models_path = ARGV.pop
51
+ match = /.yml:(\d+)$/.match(test_models_path)
52
+
53
+ command.test_index = match.captures.first.to_i unless match.nil?
54
+ command.execution_path = test_models_path.sub(/:(\d+)$/, '') if test_models_path
55
+
56
+ command
57
+ rescue Exception => ex
58
+ puts " - Error: #{ex}" unless ex.instance_of? SystemExit
59
+ exit -1
60
+ end
61
+ end
62
+
63
+ def diff_time(inicio, fim)
64
+ diferenca = (fim - inicio)
65
+ s = (diferenca % 60).to_i
66
+ m = (diferenca / 60).to_i
67
+ h = (m / 60).to_i
68
+
69
+ if s > 0
70
+ "#{(h < 10) ? '0' + h.to_s : h}:#{(m < 10) ? '0' + m.to_s : m}:#{(s < 10) ? '0' + s.to_s : s}"
71
+ else
72
+ format("%.5f", diferenca) + " s."
73
+ end
74
+ end
75
+
76
+ class << self
77
+ def format_ws_model_path(file)
78
+ format_app_path 'ws.test.models.path', file
79
+ end
80
+
81
+ def format_ws_output_path(file)
82
+ format_app_path 'ws.test.output.files.path', file
83
+ end
84
+
85
+ def format_app_path(key, file)
86
+ f = file.sub Regexp.new("^#{ENV[key]}"), ''
87
+ File.join(ENV[key], f)
88
+ end
89
+
90
+ def load_support_files
91
+ Dir.glob(File.join(Dir.pwd, "support", "**", "*.rb"))
92
+ .sort_by {|file| file.scan(File::SEPARATOR).size }
93
+ .each {|file| require file }
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,49 @@
1
+ HOOKS = {}
2
+
3
+ def before_app(&block)
4
+ if HOOKS[:before_app].nil?
5
+ HOOKS[:before_app] = [block]
6
+ else
7
+ HOOKS[:before_app] << block
8
+ end
9
+ end
10
+
11
+ def after_app(&block)
12
+ if HOOKS[:after_app].nil?
13
+ HOOKS[:after_app] = [block]
14
+ else
15
+ HOOKS[:after_app] << block
16
+ end
17
+ end
18
+
19
+ def before_suite(&block)
20
+ if HOOKS[:before_suite].nil?
21
+ HOOKS[:before_suite] = [block]
22
+ else
23
+ HOOKS[:before_suite] << block
24
+ end
25
+ end
26
+
27
+ def after_suite(&block)
28
+ if HOOKS[:after_suite].nil?
29
+ HOOKS[:after_suite] = [block]
30
+ else
31
+ HOOKS[:after_suite] << block
32
+ end
33
+ end
34
+
35
+ def before_test(&block)
36
+ if HOOKS[:before_test].nil?
37
+ HOOKS[:before_test] = [block]
38
+ else
39
+ HOOKS[:before_test] << block
40
+ end
41
+ end
42
+
43
+ def after_test(&block)
44
+ if HOOKS[:after_test].nil?
45
+ HOOKS[:after_test] = [block]
46
+ else
47
+ HOOKS[:after_test] << block
48
+ end
49
+ end