moto 0.0.33 → 0.0.40

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b186db0c69b5623b309a299d29cf4d8b0de8764d
4
- data.tar.gz: d5aa6f1066e88d132c36bfb0267b288241ae67e2
3
+ metadata.gz: 8b5492188f2eeb8cc2270194377f7da621456203
4
+ data.tar.gz: 95f4afebe273fdcf4a6a5482457f0193aee3dc6c
5
5
  SHA512:
6
- metadata.gz: 24c72f61c55a48defac80689b89313401965f75700868eb7ca2c3b6981d9ae6276729ed99e445a35297c6435cf45aec93413c200b69686f078fcca34e6d5cb88
7
- data.tar.gz: 1fb46b41e2ef571ebc51baa7e987cc4252e91cec81daa6ff28d6e5b82ae9c6d1cd92f381de462940d660ec0c72a3833b9350426f12671679ddd2f1b14e124685
6
+ metadata.gz: 8f0d02d6cffea6babcb5126ae4e0c538a8600fce9617e6566a2709dbd582e3de82b54eeb47f414166aa19fce854022966a2b36c703071df84861b061e9052bcb
7
+ data.tar.gz: 4d636279703d273404c25ca98c6c83a74676da594daf0d33ee6fc87854a743743cc5cc5a935b3fe8519dec776046527ffcb114f1d0acb44ae4192694dbc282d5
data/lib/cli.rb CHANGED
@@ -16,9 +16,9 @@ require_relative './empty_listener'
16
16
  require_relative './forward_context_methods'
17
17
  require_relative './test_logging'
18
18
  require_relative './runner_logging'
19
- require_relative './runner'
20
- require_relative './thread_context'
21
- require_relative './thread_pool'
19
+ require_relative './runner/test_runner'
20
+ require_relative './runner/thread_context'
21
+ require_relative './runner/test_generator'
22
22
  require_relative './test/base'
23
23
  require_relative './page'
24
24
  require_relative './version'
@@ -28,7 +28,6 @@ require_relative './reporting/listeners/console'
28
28
  require_relative './reporting/listeners/console_dots'
29
29
  require_relative './reporting/listeners/junit_xml'
30
30
  require_relative './reporting/listeners/webui'
31
- require_relative './test_generator'
32
31
  require_relative './exceptions/moto'
33
32
  require_relative './exceptions/test_skipped'
34
33
  require_relative './exceptions/test_forced_failure'
@@ -39,7 +38,6 @@ module Moto
39
38
  class Cli
40
39
  def self.run(argv)
41
40
  test_paths_absolute = []
42
- test_classes = []
43
41
 
44
42
  unless argv[ :tests ].nil?
45
43
  argv[ :tests ].each do |dir_name|
@@ -76,14 +74,9 @@ module Moto
76
74
  initializer.init
77
75
  end
78
76
 
79
- tg = TestGenerator.new(MotoApp::DIR)
80
- test_paths_absolute.each do |test_path|
81
- test_classes << tg.generate(test_path)
82
- end
83
-
84
- @test_reporter = Moto::Reporting::TestReporter.new(argv[:listeners], argv[:config], argv[:name])
77
+ test_reporter = Moto::Reporting::TestReporter.new(argv[:listeners], argv[:config], argv[:name])
85
78
 
86
- runner = Moto::Runner.new(test_classes, argv[:environments], argv[:config], @test_reporter)
79
+ runner = Moto::Runner::TestRunner.new(test_paths_absolute, argv[:environments], argv[:config], test_reporter)
87
80
  runner.run
88
81
  end
89
82
 
@@ -17,11 +17,11 @@ module Moto
17
17
 
18
18
  def start_run
19
19
  # TODO: make session driver configurable
20
- if context.runner.my_config[:capybara][:default_selector]
21
- Capybara.default_selector = context.runner.my_config[:capybara][:default_selector]
20
+ if context.moto_app_config[:capybara][:default_selector]
21
+ Capybara.default_selector = context.moto_app_config[:capybara][:default_selector]
22
22
  end
23
23
 
24
- Thread.current['capybara_session'] = Capybara::Session.new(context.runner.my_config[:capybara][:default_driver])
24
+ Thread.current['capybara_session'] = Capybara::Session.new(context.moto_app_config[:capybara][:default_driver])
25
25
  @pages = {}
26
26
  end
27
27
 
@@ -53,7 +53,7 @@ module Moto
53
53
  private
54
54
 
55
55
  def register_grid_driver
56
- grid_config = context.runner.my_config[:capybara][:grid]
56
+ grid_config = context.moto_app_config[:capybara][:grid]
57
57
  return if grid_config.nil?
58
58
  if grid_config[:capabilities].nil?
59
59
  capabilities = Selenium::WebDriver::Remote::Capabilities.firefox
@@ -1,21 +1,24 @@
1
- module Moto
2
- module ForwardContextMethods
3
-
4
- def client(name)
5
- @context.client(name)
6
- end
7
-
8
- def logger
9
- @context.logger
10
- end
11
-
12
- def const(key)
13
- @context.const(key)
14
- end
15
-
16
- def current_test
17
- @context.current_test
18
- end
19
-
20
- end
1
+ module Moto
2
+ module ForwardContextMethods
3
+ # Access client object instance for given class name.
4
+ def client(name)
5
+ @context.client(name)
6
+ end
7
+
8
+ # Write message to test execution log file. See Ruby Logger class for details.
9
+ def logger
10
+ @context.logger
11
+ end
12
+
13
+ # Read const value specific for current environment from `config/const.yml` file
14
+ def const(key)
15
+ @context.const(key)
16
+ end
17
+
18
+ # Access currently executed test
19
+ def test
20
+ @context.test
21
+ end
22
+
23
+ end
21
24
  end
data/lib/parser.rb CHANGED
@@ -7,7 +7,7 @@ require_relative '../lib/app_generator'
7
7
  module Moto
8
8
 
9
9
  class Parser
10
-
10
+
11
11
  def self.run(argv)
12
12
  begin
13
13
 
@@ -26,7 +26,7 @@ module Moto
26
26
  puts e.backtrace.join("\n")
27
27
  end
28
28
  end
29
-
29
+
30
30
  def self.run_parse(argv)
31
31
 
32
32
  # TODO: fix this dumb verification of current working directory.
@@ -34,11 +34,11 @@ module Moto
34
34
  msg = "Config file (config/moto.rb) not present.\n"
35
35
  msg << 'Does current working directory contain Moto application?'
36
36
  raise msg
37
- end
37
+ end
38
38
 
39
39
  require 'bundler/setup'
40
40
  Bundler.require
41
-
41
+
42
42
  # Default options
43
43
  options = {}
44
44
  options[:listeners] = []
@@ -46,33 +46,33 @@ module Moto
46
46
  options[:config] = eval(File.read("#{MotoApp::DIR}/config/moto.rb"))
47
47
  options[:environments] = []
48
48
  options[:name] = ''
49
-
49
+
50
50
  # Parse arguments
51
51
  # TODO eval ?
52
52
  # TODO const
53
- # TODO reporters should be consts - not strings
53
+ # TODO listeners should be consts - not strings
54
54
  OptionParser.new do |opts|
55
- opts.on('-t', '--tests Tests', Array) { |v| options[:tests ] = v }
56
- opts.on('-g', '--tags Tags', Array) { |v| options[:tags ] = v }
57
- opts.on('-l', '--listeners Listeners', Array) { |v| options[:listeners] = v }
55
+ opts.on('-t', '--tests Tests', Array) { |v| options[:tests ] = v }
56
+ opts.on('-g', '--tags Tags', Array) { |v| options[:tags ] = v }
57
+ opts.on('-l', '--listeners Listeners', Array) { |v| options[:listeners] = v }
58
58
  opts.on('-e', '--environments Environment', Array) { |v| options[:environments] = v }
59
- opts.on('-c', '--const Const') { |v| options[:const] = v }
60
- opts.on('-n', '--name Name') { |v| options[:name] = v }
61
- opts.on('-f', '--config Config') { |v| options[:config].deep_merge!( eval( File.read(v) ) ) }
59
+ opts.on('-c', '--const Const') { |v| options[:const] = v }
60
+ opts.on('-n', '--name Name') { |v| options[:name] = v }
61
+ opts.on('-f', '--config Config') { |v| options[:config].deep_merge!( eval( File.read(v) ) ) }
62
62
  end.parse!
63
63
 
64
64
  if options[:name].empty?
65
65
  options[:name] = evaluate_name(options[:tags], options[:tests])
66
66
  end
67
67
 
68
- if options[ :config ][ :moto ][ :runner ][ :mandatory_environment ] && options[ :environments ].empty?
68
+ if options[ :config ][ :moto ][ :test_runner ][ :mandatory_environment ] && options[ :environments ].empty?
69
69
  puts 'Environment is mandatory for this project.'
70
70
  exit 1
71
71
  end
72
72
 
73
73
  return options
74
74
  end
75
-
75
+
76
76
  def self.evaluate_name(tags, tests)
77
77
  tags ||= ''
78
78
  tests ||= ''
@@ -104,19 +104,20 @@ module Moto
104
104
 
105
105
  return options
106
106
  end
107
-
107
+
108
108
  def self.show_help
109
109
  puts """
110
110
  Moto (#{Moto::VERSION}) CLI Help:
111
111
  moto --version Display current version
112
112
 
113
113
  moto run:
114
- -t, --tests = Tests to be executed.
115
- For eg. Tests\Failure\Failure.rb should be passed as Tests::Failure
116
- -r, --reporter = Reporters to be used.
117
- Defaults are Moto::Listeners::ConsoleDots, Moto::Listeners::JunitXml
114
+ -t, --tests = Tests to be executed.
115
+ For eg. Tests\Failure\Failure.rb should be passed as Tests::Failure
116
+ -l, --listeners = Reporters to be used.
117
+ Defaults are Moto::Listeners::ConsoleDots, Moto::Listeners::JunitXml
118
118
  -e, --environment etc etc
119
119
 
120
+
120
121
  moto generate:
121
122
  -t, --test = Path and name of the test to be created.
122
123
  Examples:
@@ -133,6 +134,6 @@ module Moto
133
134
  You have been warned.
134
135
  """
135
136
  end
136
-
137
+
137
138
  end
138
139
  end
@@ -4,7 +4,7 @@ module Moto
4
4
  class Console < Base
5
5
 
6
6
  def start_run
7
- puts 'START'
7
+ # puts 'START'
8
8
  end
9
9
 
10
10
  def end_run(run_status)
@@ -18,11 +18,11 @@ module Moto
18
18
  end
19
19
 
20
20
  def start_test(test_status)
21
- print test_status.name
21
+ # puts test_status.name
22
22
  end
23
23
 
24
24
  def end_test(test_status)
25
- puts "\t#{test_status.to_s}"
25
+ puts "\n#{test_status.name}\n\t#{test_status.results.last.message}"
26
26
  end
27
27
 
28
28
  end
@@ -28,7 +28,7 @@ module Moto
28
28
  puts 'ERRORS: '
29
29
  run_status.tests_error.each do |test_status|
30
30
  puts test_status.name
31
- puts test_status.results.last.message
31
+ puts "\t" + test_status.results.last.message
32
32
  puts ''
33
33
  end
34
34
  end
@@ -38,7 +38,7 @@ module Moto
38
38
  puts 'SKIPPED: '
39
39
  run_status.tests_skipped.each do |test_status|
40
40
  puts test_status.name
41
- puts test_status.results.last.message
41
+ puts "\t" + test_status.results.last.message
42
42
  puts ''
43
43
  end
44
44
  end
@@ -12,7 +12,7 @@ module Moto
12
12
  def initialize(listeners, config, custom_run_name)
13
13
 
14
14
  if listeners.empty?
15
- config[:moto][:runner][:default_listeners].each do |listener_class_name|
15
+ config[:moto][:test_runner][:default_listeners].each do |listener_class_name|
16
16
  listeners << listener_class_name
17
17
  end
18
18
  else
@@ -0,0 +1,165 @@
1
+ module MotoApp
2
+ module Tests
3
+ end
4
+ end
5
+
6
+ module Moto
7
+ module Runner
8
+ class TestGenerator
9
+
10
+ def initialize(environments)
11
+ @internal_counter = 0
12
+ @environments = environments
13
+ end
14
+
15
+ # Method returns an array of test instances that represent all variants (parameter sets from test's config).
16
+ # Example: A test with no config file will be returned as an array with single Moto::Test::Base in it.
17
+ # Example: A test with a config file and 2 sets of parameters there will be returned as array with two elements.
18
+ #
19
+ # @param [String] test_path_absolute Path to the test that is about to be instantiated.
20
+ # @return [Array] An array of [Moto::Test::Base] decendants
21
+ # each entry is a Test with set of parameters injected
22
+ def get_test_with_variants(test_path_absolute)
23
+ test_path_absolute ? variantize(test_path_absolute) : nil
24
+ end
25
+
26
+ # Converts test's path to an array of Moto::Base::Test instances that represent all test variants (params, envs)
27
+ #
28
+ # *IMPORTANT*
29
+ # Config files with ruby code will be evaluated thus if you use any classes in them
30
+ # they must be required prior to that. That might be done in overloaded app's initializer.
31
+ #
32
+ # @param [String] test_path_absolute Path to the file with test
33
+ # @return [Array] array of already initialized test's variants
34
+ def variantize(test_path_absolute)
35
+ variants = []
36
+
37
+ @environments.each do |env|
38
+ params_path = test_path_absolute.sub(/\.rb\z/, '')
39
+
40
+ if File.exists?(params_path)
41
+ begin
42
+ params_all = eval(File.read(params_path))
43
+ rescue Exception => e
44
+ puts "Parameters error:\n\t File: #{params_path}\n\t Error: #{e.message}\n\n"
45
+ end
46
+ else
47
+ params_all = [{}]
48
+ end
49
+
50
+ params_all.each_with_index do |params, params_index|
51
+
52
+ # Filtering out param sets that are specific to certain envs
53
+ unless params['__env'].nil?
54
+ allowed_envs = params['__env'].is_a?(String) ? [params['__env']] : params['__env']
55
+ next unless allowed_envs.include? env
56
+ end
57
+
58
+ test = generate(test_path_absolute)
59
+ test.init(env, params, params_index, @internal_counter)
60
+ test.log_path = "#{test.dir}/logs/#{test.name.gsub(/[^0-9A-Za-z.\-]/, '_')}.log"
61
+ @internal_counter += 1
62
+ variants << test
63
+ end
64
+ end
65
+
66
+ variants
67
+ end
68
+ private :variantize
69
+
70
+ # assuming that target file includes only content of method 'run' and some magic comments
71
+ def generate(test_path_absolute)
72
+ method_body = File.read(test_path_absolute) + "\n"
73
+
74
+ full_code = !!method_body.match(/^#\s*FULL_CODE\s+/)
75
+
76
+ if full_code
77
+ generate_for_full_class_code(test_path_absolute)
78
+ else
79
+ generate_for_run_body(test_path_absolute, method_body)
80
+ end
81
+ end
82
+ private :generate
83
+
84
+ # Generates test instances, based on fully defined class file
85
+ # @return [Moto::Test::Base]
86
+ def generate_for_full_class_code(test_path_absolute)
87
+ require test_path_absolute
88
+
89
+ test_object = nil
90
+
91
+ # Checking if it's possible to create test based on provided path. In case something is wrong with
92
+ # modules structure in class itself Moto::Test::Base will be instantized with raise injected into its run()
93
+ # so we can have proper reporting and summary even if the test doesn't execute.
94
+ begin
95
+ class_name = test_path_absolute.gsub("#{MotoApp::DIR}/", 'moto_app/').camelize.chomp('.rb').constantize
96
+ test_object = class_name.new
97
+ rescue NameError
98
+ class_name = Moto::Test::Base
99
+ test_object = class_name.new
100
+
101
+ class << test_object
102
+ attr_accessor :custom_name
103
+
104
+ def run
105
+ raise "ERROR: Invalid module structure: #{custom_name}"
106
+ end
107
+ end
108
+
109
+ test_object.custom_name = test_path_absolute.gsub("#{MotoApp::DIR}/", 'moto_app/').camelize.chomp('.rb')
110
+ end
111
+
112
+ test_object.static_path = test_path_absolute
113
+ test_object.evaled = false
114
+ test_object
115
+ end
116
+ private :generate_for_full_class_code
117
+
118
+ def create_module_tree(root_module, next_modules)
119
+ return root_module if next_modules.empty?
120
+ next_module_name = next_modules.shift
121
+ if root_module.const_defined?(next_module_name.to_sym)
122
+ m = root_module.const_get(next_module_name.to_sym)
123
+ else
124
+ m = Module.new
125
+ root_module.const_set(next_module_name.to_sym, m)
126
+ end
127
+ create_module_tree(m, next_modules)
128
+ end
129
+ private :create_module_tree
130
+
131
+ # Generates test instances, based on a text file with ruby code that has to be injected into run() method
132
+ # @return [Moto::Test::Base]
133
+ def generate_for_run_body(test_path_absolute, method_body)
134
+ base = Moto::Test::Base
135
+ base_class_string = method_body.match(/^#\s*BASE_CLASS:\s(\S+)/)
136
+ unless base_class_string.nil?
137
+ base_class_string = base_class_string[1].strip
138
+
139
+ a = base_class_string.underscore.split('/')
140
+ base_test_path = a[1..-1].join('/')
141
+
142
+ require "#{MotoApp::DIR}/#{base_test_path}"
143
+ base = base_class_string.constantize
144
+ end
145
+
146
+ # MotoApp::Tests::Login::Short
147
+ consts = test_path_absolute.camelize.split('Tests::')[1].split('::')
148
+ consts.pop
149
+ class_name = consts.pop
150
+
151
+ m = create_module_tree(MotoApp::Tests, consts)
152
+ cls = Class.new(base)
153
+ m.const_set(class_name.to_sym, cls)
154
+
155
+ test_object = cls.new
156
+ test_object.instance_eval("def run\n #{method_body} \n end")
157
+ test_object.static_path = test_path_absolute
158
+ test_object.evaled = true
159
+ test_object
160
+ end
161
+ private :generate_for_run_body
162
+
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,69 @@
1
+ require 'thread'
2
+ require_relative 'test_generator'
3
+
4
+ module Moto
5
+ module Runner
6
+ # Thread safe provider of test instances
7
+ class TestProvider
8
+
9
+ # @param [Array] test_paths_absolute
10
+ # @param [Array] environments Array
11
+ # @param [Integer] test_repeats
12
+ def initialize(test_paths_absolute, environments, test_repeats)
13
+ super()
14
+ @test_repeats = test_repeats
15
+ @current_test_repeat = 1
16
+ @queue = Queue.new
17
+ @test_paths_absolute = test_paths_absolute
18
+ @test_generator = TestGenerator.new(environments)
19
+ end
20
+
21
+ # Use this to retrieve tests safely in multithreaded environment
22
+ def get_test
23
+ create_tests
24
+ @queue.pop
25
+ end
26
+
27
+ # Pushes new tests to the queue if possible and the queue is already empty
28
+ def create_tests
29
+ if @queue.empty?
30
+
31
+ test_variants = @test_generator.get_test_with_variants(get_test_path)
32
+
33
+ if test_variants
34
+ test_variants.each do |test|
35
+ @queue.push(test)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ private :create_tests
42
+
43
+ # Returns path to the test while supporting the number of repeats specified by the user
44
+ # return [String] Path to the test
45
+ def get_test_path
46
+
47
+ if @current_test_repeat == 1
48
+ @test_path = @test_paths_absolute.shift
49
+ end
50
+
51
+ if @current_test_repeat == @test_repeats
52
+ @current_test_repeat = 1
53
+ else
54
+ @current_test_repeat += 1
55
+ end
56
+
57
+ @test_path
58
+ end
59
+ private :get_test_path
60
+
61
+ # Number of threads waiting for a job
62
+ def num_waiting
63
+ @queue.num_waiting
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,65 @@
1
+ require_relative '../reporting/test_reporter'
2
+ require_relative './test_provider'
3
+
4
+ module Moto
5
+ module Runner
6
+ class TestRunner
7
+
8
+ attr_reader :logger
9
+ attr_reader :config
10
+ attr_reader :test_reporter
11
+
12
+ def initialize(test_paths_absolute, environments, config, test_reporter)
13
+ @test_paths_absolute = test_paths_absolute
14
+ @config = config
15
+ @test_reporter = test_reporter
16
+
17
+ # TODO: initialize logger from config (yml or just ruby code)
18
+ @logger = Logger.new(File.open("#{MotoApp::DIR}/moto.log", File::WRONLY | File::APPEND | File::CREAT))
19
+
20
+ @environments = environments.empty? ? environments << :__default : environments
21
+ end
22
+
23
+ def running_thread_count
24
+ Thread.list.select {|thread| thread.status == "run"}.count
25
+ end
26
+
27
+ def run
28
+ test_provider = TestProvider.new(@test_paths_absolute, @environments, @config[:moto][:test_runner][:test_repeats])
29
+ threads_max = @config[:moto][:test_runner][:thread_count] || 1
30
+
31
+ # remove log/screenshot files from previous execution
32
+ @test_paths_absolute.each do |test_path|
33
+ FileUtils.rm_rf("#{File.dirname(test_path)}/logs")
34
+ end
35
+
36
+ @test_reporter.report_start_run
37
+
38
+ # Create as many threads as we're allowed by the config file.
39
+ # test_provider.get_test - will invoke Queue.pop thus putting the thread to sleep
40
+ # once there is no more work.
41
+ (1..threads_max).each do |index|
42
+ Thread.new do
43
+ Thread.current[:id] = index
44
+ loop do
45
+ tc = ThreadContext.new(@config, test_provider.get_test, @test_reporter)
46
+ tc.run
47
+ end
48
+ end
49
+ end
50
+
51
+ # Waiting for all threads to run out of work so we can end the application
52
+ loop do
53
+ if test_provider.num_waiting == threads_max
54
+ break
55
+ end
56
+
57
+ sleep 1
58
+ end
59
+
60
+ @test_reporter.report_end_run
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,146 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+ module Moto
5
+ module Runner
6
+ class ThreadContext
7
+
8
+ # all resources specific for single thread will be initialized here. E.g. browser session
9
+ attr_reader :logger
10
+ attr_reader :test
11
+ attr_reader :moto_app_config
12
+
13
+ def initialize(config, test, test_reporter)
14
+ @config = config
15
+ @test = test
16
+ @clients = {}
17
+ @test.context = self
18
+ @test_reporter = test_reporter
19
+
20
+ # TODO: temporary fix
21
+ Thread.current['context'] = self
22
+
23
+ # TODO: temporary fix
24
+ @moto_app_config = {}
25
+ Dir.glob('config/*.yml').each do |f|
26
+ @moto_app_config.deep_merge! YAML.load_file(f)
27
+ end
28
+ end
29
+
30
+ def client(name)
31
+ return @clients[name] if @clients.key? name
32
+
33
+ name_app = 'MotoApp::Lib::Clients::' + name
34
+ name_moto = 'Moto::Clients::' + name
35
+
36
+ c = try_client(name_app, "#{MotoApp::DIR}/")
37
+ unless c.nil?
38
+ @clients[name] = c
39
+ return c
40
+ end
41
+
42
+ c = try_client(name_moto, "#{Moto::DIR}/lib")
43
+ unless c.nil?
44
+ @clients[name] = c
45
+ return c
46
+ end
47
+ raise "Could not find client class for name #{name}"
48
+ end
49
+
50
+ def try_client(name, dir)
51
+ begin
52
+ a = name.underscore.split('/')
53
+ client_path = a[1..-1].join('/')
54
+ require "#{dir}/#{client_path}"
55
+ client_const = name.constantize
56
+ instance = client_const.new(self)
57
+ instance.init
58
+ instance.start_run
59
+ instance.start_test(@test)
60
+ return instance
61
+ rescue Exception => e
62
+ # puts e
63
+ # puts e.backtrace
64
+ return nil
65
+ end
66
+ end
67
+
68
+ def const(key)
69
+ key = key.to_s
70
+ key = "#{@test.env.to_s}.#{key}" if @test.env != :__default
71
+ code = if key.include? '.'
72
+ "@moto_app_config#{key.split('.').map { |a| "['#{a}']" }.join('')}"
73
+ else
74
+ "@moto_app_config['#{key}']"
75
+ end
76
+ begin
77
+ v = eval code
78
+ raise if v.nil?
79
+ rescue
80
+ raise "There is no const defined for key: #{key}. Environment: #{ (@test.env == :__default) ? '<none>' : @test.env }"
81
+ end
82
+ v
83
+ end
84
+
85
+ def run
86
+ max_attempts = @config[:moto][:thread_context][:max_attempts] || 1
87
+ sleep_time = @config[:moto][:thread_context][:sleep_before_attempt] || 0
88
+
89
+ log_directory = File.dirname(@test.log_path)
90
+ if !File.directory?(log_directory)
91
+ FileUtils.mkdir_p(log_directory)
92
+ end
93
+
94
+ @logger = Logger.new(File.open(@test.log_path, File::WRONLY | File::TRUNC | File::CREAT))
95
+ @logger.level = @config[:moto][:thread_context][:log_level] || Logger::DEBUG
96
+
97
+ # Reporting: start_test
98
+ @test_reporter.report_start_test(@test.status)
99
+
100
+ (1..max_attempts).each do |attempt|
101
+
102
+ @clients.each_value { |c| c.start_test(@test) }
103
+ @test.before
104
+ @logger.info "Start: #{@test.name} attempt #{attempt}/#{max_attempts}"
105
+
106
+ begin
107
+ @test.run_test
108
+ rescue Exceptions::TestForcedPassed, Exceptions::TestForcedFailure, Exceptions::TestSkipped => e
109
+ @logger.info(e.message)
110
+ rescue Exception => e
111
+ @logger.error("#{e.class.name}: #{e.message}")
112
+ @logger.error(e.backtrace.join("\n"))
113
+ @clients.each_value { |c| c.handle_test_exception(@test, e) }
114
+ end
115
+
116
+ @test.after
117
+ @clients.each_value { |c| c.end_test(@test) }
118
+
119
+ @logger.info("Result: #{@test.status.results.last.code}")
120
+
121
+ # test should have another attempt only in case of an error
122
+ # pass, skip and fail statuses end attempts
123
+ if @test.status.results.last.code != Moto::Test::Result::ERROR
124
+ break
125
+ end
126
+
127
+ # don't go to sleep in the last attempt
128
+ if attempt < max_attempts
129
+ sleep sleep_time
130
+ end
131
+
132
+ end # Make another attempt
133
+
134
+ # Close and flush stream to file
135
+ @logger.close
136
+
137
+ # Reporting: end_test
138
+ @test_reporter.report_end_test(@test.status)
139
+
140
+ @clients.each_value { |c| c.end_run }
141
+
142
+ end
143
+
144
+ end
145
+ end
146
+ end
data/lib/test/base.rb CHANGED
@@ -27,10 +27,10 @@ module Moto
27
27
  end
28
28
 
29
29
  # Initializes test to be executed with specified params and environment
30
- def init(env, params, params_index)
30
+ def init(env, params, params_index, global_index)
31
31
  @env = env
32
32
  @params = params
33
- @name = generate_name(params_index)
33
+ @name = generate_name(params_index, global_index)
34
34
 
35
35
  @status = Moto::Test::Status.new
36
36
  @status.name = @name
@@ -42,16 +42,19 @@ module Moto
42
42
  # Generates name of the test based on its properties:
43
43
  # - number/name of currently executed configuration run
44
44
  # - env
45
- def generate_name(params_index)
45
+ def generate_name(params_index, global_index)
46
+ simple_class_name = self.class.to_s.demodulize
47
+
46
48
  if @env == :__default
47
- return "#{self.class.to_s}" if @params.empty?
48
- return "#{self.class.to_s}/#{@params['__name']}" if @params.key?('__name')
49
- return "#{self.class.to_s}/params_#{params_index}" unless @params.key?('__name')
49
+ return "#{simple_class_name}_#{global_index}" if @params.empty?
50
+ return "#{simple_class_name}_#{@params[:__name]}_#{global_index}" if @params.key?(:__name)
51
+ return "#{simple_class_name}_P#{params_index}_#{global_index}" unless @params.key?(:__name)
50
52
  else
51
- return "#{self.class.to_s}/#{@env}" if @params.empty?
52
- return "#{self.class.to_s}/#{@env}/#{@params['__name']}" if @params.key?('__name')
53
- return "#{self.class.to_s}/#{@env}/params_#{params_index}" unless @params.key?('__name')
53
+ return "#{simple_class_name}_#{@env}_##{global_index}" if @params.empty?
54
+ return "#{simple_class_name}_#{@env}_#{@params[:__name]}_#{global_index}" if @params.key?(:__name)
55
+ return "#{simple_class_name}_#{@env}_P#{params_index}_#{global_index}" unless @params.key?(:__name)
54
56
  end
57
+
55
58
  self.class.to_s
56
59
  end
57
60
  private :generate_name
data/lib/test_logging.rb CHANGED
@@ -36,10 +36,10 @@ module Moto
36
36
  end
37
37
  end
38
38
  end
39
- @context.current_test.logger.debug("ENTER >>> #{self.class.name}::#{__callee__}(#{args})") unless skip_logging
39
+ @context.test.logger.debug("ENTER >>> #{self.class.name}::#{__callee__}(#{args})") unless skip_logging
40
40
  result = send original_method, *args
41
41
  # Below is the hack to properly escape binary data (if any manages to make it to logs)
42
- @context.current_test.logger.debug("LEAVE <<< #{self.class.name}::#{__callee__} => #{[result].to_s[1..-2]}") unless skip_logging
42
+ @context.test.logger.debug("LEAVE <<< #{self.class.name}::#{__callee__} => #{[result].to_s[1..-2]}") unless skip_logging
43
43
  result
44
44
  end
45
45
  @added = false
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Moto
2
- VERSION = '0.0.33'
2
+ VERSION = '0.0.40'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.33
4
+ version: 0.0.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bartek Wilczek
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2016-04-04 00:00:00.000000000 Z
14
+ date: 2016-04-21 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activesupport
@@ -104,15 +104,15 @@ files:
104
104
  - lib/reporting/listeners/webui.rb
105
105
  - lib/reporting/run_status.rb
106
106
  - lib/reporting/test_reporter.rb
107
- - lib/runner.rb
107
+ - lib/runner/test_generator.rb
108
+ - lib/runner/test_provider.rb
109
+ - lib/runner/test_runner.rb
110
+ - lib/runner/thread_context.rb
108
111
  - lib/runner_logging.rb
109
112
  - lib/test/base.rb
110
113
  - lib/test/result.rb
111
114
  - lib/test/status.rb
112
- - lib/test_generator.rb
113
115
  - lib/test_logging.rb
114
- - lib/thread_context.rb
115
- - lib/thread_pool.rb
116
116
  - lib/version.rb
117
117
  homepage: https://github.com/bwilczek/moto
118
118
  licenses:
data/lib/runner.rb DELETED
@@ -1,62 +0,0 @@
1
- require_relative './reporting/test_reporter'
2
-
3
- module Moto
4
- class Runner
5
-
6
- attr_reader :logger
7
- attr_reader :environments
8
- attr_reader :assert
9
- attr_reader :config
10
- attr_reader :name
11
- attr_reader :test_reporter
12
-
13
- def initialize(tests, environments, config, test_reporter)
14
- @tests = tests
15
- @config = config
16
- @thread_pool = ThreadPool.new(my_config[:thread_count] || 1)
17
- @test_reporter = test_reporter
18
-
19
- # TODO: initialize logger from config (yml or just ruby code)
20
- # @logger = Logger.new(STDOUT)
21
- @logger = Logger.new(File.open("#{MotoApp::DIR}/moto.log", File::WRONLY | File::APPEND | File::CREAT))
22
- # @logger.level = Logger::WARN
23
-
24
- # TODO: validate envs, maybe no-env should be supported as well?
25
- environments << :__default if environments.empty?
26
- @environments = environments
27
- end
28
-
29
- # TODO: Remake
30
- # @return [Hash] hash with config
31
- def my_config
32
- caller_path = caller.first.to_s.split(/:\d/)[0]
33
- keys = []
34
- if caller_path.include? MotoApp::DIR
35
- caller_path.sub!( "#{MotoApp::DIR}/lib/", '' )
36
- keys << 'moto_app'
37
- elsif caller_path.include? Moto::DIR
38
- caller_path.sub!( "#{Moto::DIR}/lib/", '' )
39
- keys << 'moto'
40
- end
41
- caller_path.sub!('.rb', '')
42
- keys << caller_path.split('/')
43
- keys.flatten!
44
- eval "@config#{keys.map{|k| "[:#{k}]" }.join('')}"
45
- end
46
-
47
- def run
48
- @test_reporter.report_start_run
49
-
50
- @tests.each do |test|
51
- @thread_pool.schedule do
52
- tc = ThreadContext.new(self, test, @test_reporter)
53
- tc.run
54
- end
55
- end
56
-
57
- @thread_pool.shutdown
58
- @test_reporter.report_end_run
59
- end
60
-
61
- end
62
- end
@@ -1,112 +0,0 @@
1
- module MotoApp
2
- module Tests
3
- end
4
- end
5
-
6
- module Moto
7
- class TestGenerator
8
-
9
- def initialize(app_dir)
10
- @app_dir = app_dir
11
- end
12
-
13
- # assuming that target file includes full valid ruby class
14
- def create(class_name)
15
- class_name = 'MotoApp::Tests::'+class_name
16
- a = class_name.underscore.split('/')
17
- test_path = (a[1..20]+[a[-1]]).join('/')
18
-
19
- # TODO: check if this path and constant exists
20
- require "#{MotoApp::DIR}/#{test_path}"
21
- test_const = class_name.safe_constantize
22
- test_const.new
23
- end
24
-
25
- def create_module_tree(root_module, next_modules)
26
- return root_module if next_modules.empty?
27
- next_module_name = next_modules.shift
28
- if root_module.const_defined?(next_module_name.to_sym)
29
- m = root_module.const_get(next_module_name.to_sym)
30
- else
31
- m = Module.new
32
- root_module.const_set(next_module_name.to_sym, m)
33
- end
34
- create_module_tree(m, next_modules)
35
- end
36
-
37
- # assuming that target file includes only content of method 'run' and some magic comments
38
- def generate(test_path_absolute)
39
- method_body = File.read(test_path_absolute) + "\n"
40
-
41
- full_code = !! method_body.match( /^#\s*FULL_CODE\s+/ )
42
-
43
- if full_code
44
- generate_for_full_class_code(test_path_absolute)
45
- else
46
- generate_for_run_body(test_path_absolute, method_body)
47
- end
48
- end
49
-
50
- # Generates objects with tests that are supposed to be executed in this run.
51
- def generate_for_full_class_code(test_path_absolute)
52
- require test_path_absolute
53
-
54
- test_object = nil
55
-
56
- # Checking if it's possible to create test based on provided path. In case something is wrong with
57
- # modules structure in class itself Moto::Test::Base will be instantized with raise injected into its run()
58
- # so we can have proper reporting and summary even if the test doesn't execute.
59
- begin
60
- class_name = test_path_absolute.gsub("#{MotoApp::DIR}/",'moto_app/').camelize.chomp('.rb').constantize
61
- test_object = class_name.new
62
- rescue NameError
63
- class_name = Moto::Test::Base
64
- test_object = class_name.new
65
-
66
- class << test_object
67
- attr_accessor :custom_name
68
-
69
- def run
70
- raise "ERROR: Invalid module structure: #{custom_name}"
71
- end
72
- end
73
-
74
- test_object.custom_name = test_path_absolute.gsub("#{MotoApp::DIR}/",'moto_app/').camelize.chomp('.rb')
75
- end
76
-
77
- test_object.static_path = test_path_absolute
78
- test_object.evaled = false
79
- test_object
80
- end
81
-
82
- def generate_for_run_body(test_path_absolute, method_body)
83
- base = Moto::Test::Base
84
- base_class_string = method_body.match( /^#\s*BASE_CLASS:\s(\S+)/ )
85
- unless base_class_string.nil?
86
- base_class_string = base_class_string[1].strip
87
-
88
- a = base_class_string.underscore.split('/')
89
- base_test_path = a[1..-1].join('/')
90
-
91
- require "#{MotoApp::DIR}/#{base_test_path}"
92
- base = base_class_string.constantize
93
- end
94
-
95
- # MotoApp::Tests::Login::Short
96
- consts = test_path_absolute.camelize.split('Tests::')[1].split('::')
97
- consts.pop
98
- class_name = consts.pop
99
-
100
- m = create_module_tree(MotoApp::Tests, consts)
101
- cls = Class.new(base)
102
- m.const_set(class_name.to_sym, cls)
103
-
104
- test_object = cls.new
105
- test_object.instance_eval( "def run\n #{method_body} \n end" )
106
- test_object.static_path = test_path_absolute
107
- test_object.evaled = true
108
- test_object
109
- end
110
-
111
- end
112
- end
@@ -1,162 +0,0 @@
1
- require 'erb'
2
-
3
- module Moto
4
- class ThreadContext
5
-
6
- # all resources specific for single thread will be initialized here. E.g. browser session
7
- attr_reader :runner
8
- attr_reader :logger
9
- attr_reader :current_test
10
-
11
- def initialize(runner, test, test_reporter)
12
- @runner = runner
13
- @test = test
14
- @clients = {}
15
- @test.context = self
16
- @test_reporter = test_reporter
17
-
18
- #TODO temporary fix
19
- Thread.current['context']= self
20
-
21
- @config = {}
22
- Dir.glob("config/*.yml").each do |f|
23
- @config.deep_merge! YAML.load_file(f)
24
- end
25
- end
26
-
27
- def client(name)
28
- return @clients[name] if @clients.key? name
29
-
30
- name_app = 'MotoApp::Lib::Clients::' + name
31
- name_moto = 'Moto::Clients::' + name
32
-
33
- c = try_client(name_app, "#{MotoApp::DIR}/")
34
- unless c.nil?
35
- @clients[name] = c
36
- return c
37
- end
38
-
39
- c = try_client(name_moto, "#{Moto::DIR}/lib")
40
- unless c.nil?
41
- @clients[name] = c
42
- return c
43
- end
44
- raise "Could not find client class for name #{name}"
45
- end
46
-
47
- def try_client(name, dir)
48
- begin
49
- a = name.underscore.split('/')
50
- client_path = a[1..-1].join('/')
51
- require "#{dir}/#{client_path}"
52
- client_const = name.constantize
53
- instance = client_const.new(self)
54
- instance.init
55
- instance.start_run
56
- instance.start_test(@current_test)
57
- return instance
58
- rescue Exception => e
59
- # puts e
60
- # puts e.backtrace
61
- return nil
62
- end
63
- end
64
-
65
- def const(key)
66
- key = key.to_s
67
- key = "#{@current_test.env.to_s}.#{key}" if @current_test.env != :__default
68
- code = if key.include? '.'
69
- "@config#{key.split('.').map { |a| "['#{a}']" }.join('')}"
70
- else
71
- "@config['#{key}']"
72
- end
73
- begin
74
- v = eval code
75
- raise if v.nil?
76
- rescue
77
- raise "There is no const defined for key: #{key}. Environment: #{ (@current_test.env == :__default) ? '<none>' : @current_test.env }"
78
- end
79
- v
80
- end
81
-
82
- def run
83
- # remove log/screenshot files from previous execution
84
- Dir.glob("#{@test.dir}/*.{log,png}").each { |f| File.delete f }
85
- max_attempts = @runner.my_config[:max_attempts] || 1
86
- sleep_time = @runner.my_config[:sleep_before_attempt] || 0
87
-
88
- @runner.environments.each do |env|
89
- params_all = [{}]
90
-
91
- # RB Config files
92
- params_path = "#{@test.dir}/#{@test.filename}"
93
- params_all = eval(File.read(params_path)) if File.exists?(params_path)
94
-
95
- params_all.each_with_index do |params, params_index|
96
-
97
- # Filtering out param sets that are specific to certain envs
98
- unless params['__env'].nil?
99
- allowed_envs = params['__env'].is_a?(String) ? [params['__env']] : params['__env']
100
- next unless allowed_envs.include? env
101
- end
102
-
103
- @test.init(env, params, params_index)
104
- # @test.log_path = "#{@test.dir}/#{@test.name.gsub(/\s+/, '_').gsub(':', '_').gsub('::', '_').gsub('/', '_')}.log"
105
- @test.log_path = "#{@test.dir}/#{@test.name.demodulize.gsub('/', '_')}.log"
106
- # TODO: log path might be specified (to some extent) by the configuration
107
- @logger = Logger.new(File.open(@test.log_path, File::WRONLY | File::TRUNC | File::CREAT))
108
- @logger.level = @runner.my_config[:log_level] || Logger::DEBUG
109
- @current_test = @test
110
-
111
- (1..max_attempts).each do |attempt|
112
-
113
- # Reporting: start_test
114
- if attempt == 1
115
- @test_reporter.report_start_test(@test.status)
116
- end
117
-
118
- @clients.each_value { |c| c.start_test(@test) }
119
- @test.before
120
- @logger.info "Start: #{@test.name} attempt #{attempt}/#{max_attempts}"
121
-
122
- begin
123
- @test.run_test
124
- rescue Exceptions::TestForcedPassed, Exceptions::TestForcedFailure, Exceptions::TestSkipped => e
125
- @logger.info(e.message)
126
- rescue Exception => e
127
- @logger.error("#{e.class.name}: #{e.message}")
128
- @logger.error(e.backtrace.join("\n"))
129
- @clients.each_value { |c| c.handle_test_exception(@test, e) }
130
- end
131
-
132
- @test.after
133
- @clients.each_value { |c| c.end_test(@test) }
134
-
135
- @logger.info("Result: #{@test.status.results.last.code}")
136
-
137
- # test should have another attempt only in case of an error
138
- # pass, skip and fail statuses end attempts
139
- if @test.status.results.last.code != Moto::Test::Result::ERROR
140
- break
141
- end
142
-
143
- # don't go to sleep in the last attempt
144
- if attempt < max_attempts
145
- sleep sleep_time
146
- end
147
-
148
- end # Make another attempt
149
-
150
- # Close and flush stream to file
151
- @logger.close
152
-
153
- # Reporting: end_test
154
- @test_reporter.report_end_test(@test.status)
155
-
156
- end
157
- end
158
- @clients.each_value { |c| c.end_run }
159
- end
160
-
161
- end
162
- end
data/lib/thread_pool.rb DELETED
@@ -1,32 +0,0 @@
1
- module Moto
2
- class ThreadPool
3
-
4
- def initialize(size=1)
5
- @size = size
6
- @jobs = Queue.new
7
- @pool = Array.new(@size) do |i|
8
- Thread.new do
9
- Thread.current[:id] = i
10
- catch(:exit) do
11
- loop do
12
- job, args = @jobs.pop
13
- job.call(*args)
14
- end
15
- end
16
- end
17
- end
18
- end
19
-
20
- def schedule(*args, &block)
21
- @jobs << [block, args]
22
- end
23
-
24
- def shutdown
25
- @size.times do
26
- schedule { throw :exit }
27
- end
28
- @pool.each(&:join)
29
- end
30
-
31
- end
32
- end