moto 0.0.33 → 0.0.40

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.
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