specimen 0.0.1.alpha.b → 0.0.2.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -19
  3. data/VERSION +1 -1
  4. data/bin/specimen +2 -2
  5. data/lib/specimen/cli.rb +6 -20
  6. data/lib/specimen/command/base.rb +35 -0
  7. data/lib/specimen/command/base_group.rb +35 -0
  8. data/lib/specimen/command/exec_command_builder.rb +47 -0
  9. data/lib/specimen/command/runner/cukes_runner.rb +26 -0
  10. data/lib/specimen/command/runner/exec_runner.rb +37 -0
  11. data/lib/specimen/command/runner/path_runner.rb +24 -0
  12. data/lib/specimen/command/runner/specs_runner.rb +25 -0
  13. data/lib/specimen/command/test_runner.rb +58 -0
  14. data/lib/specimen/command.rb +209 -0
  15. data/lib/specimen/commands/cukes/cukes_command.rb +9 -0
  16. data/lib/specimen/commands/exec/exec_command.rb +9 -0
  17. data/lib/specimen/commands/gem_help/USAGE +11 -0
  18. data/lib/specimen/commands/gem_help/gem_help_command.rb +23 -0
  19. data/lib/specimen/commands/init/USAGE +38 -0
  20. data/lib/specimen/commands/init/init_command.rb +49 -0
  21. data/lib/specimen/commands/specs/specs_command.rb +9 -0
  22. data/lib/specimen/extensions/ruby/hash.rb +1 -0
  23. data/lib/specimen/generator/configs/specimen_project_config.rb +122 -0
  24. data/lib/specimen/generator/cucumber/cucumber_project_generator.rb +28 -0
  25. data/lib/specimen/generator/cucumber/templates/config/cucumber.yml.tt +1 -0
  26. data/lib/specimen/generator/cucumber/templates/features/examples/add_numbers.feature.tt +9 -0
  27. data/lib/specimen/generator/cucumber/templates/features/support/env.rb.tt +0 -0
  28. data/lib/specimen/generator/file_by_template.rb +6 -4
  29. data/lib/specimen/generator/generator_base.rb +9 -0
  30. data/lib/specimen/generator/project/project_root_generator.rb +33 -0
  31. data/lib/specimen/generator/project/specimen_project_generator.rb +25 -0
  32. data/lib/specimen/generator/project/templates/root/.gitignore.tt +4 -0
  33. data/lib/specimen/generator/project/templates/root/config/specimen.yml.tt +38 -0
  34. data/lib/specimen/generator/project_generator_base.rb +34 -0
  35. data/lib/specimen/generator/rspec/rspec_project_generator.rb +28 -0
  36. data/lib/specimen/generator/rspec/templates/config/.rspec.tt +5 -0
  37. data/lib/specimen/generator/rspec/templates/spec/examples/example_spec.rb.tt +13 -0
  38. data/lib/specimen/generator/rspec/templates/spec/spec_helper.rb.tt +15 -0
  39. data/lib/specimen/generator.rb +8 -1
  40. data/lib/specimen/runtime/yml_parser.rb +49 -0
  41. data/lib/specimen/runtime.rb +84 -0
  42. data/lib/specimen/version.rb +4 -6
  43. data/lib/specimen.rb +11 -3
  44. metadata +99 -12
  45. data/lib/specimen/cli/default_command.rb +0 -37
  46. data/lib/specimen/generator/specimen_project.rb +0 -73
  47. data/lib/specimen/generator/specimen_project_config.rb +0 -54
  48. data/lib/specimen/generator/templates/specimen/specimen.yml.tt +0 -1
  49. /data/lib/specimen/generator/{templates/gemrc.tt → project/templates/root/.gemrc.tt} +0 -0
  50. /data/lib/specimen/generator/{templates/rbenv-gemsets.tt → project/templates/root/.rbenv-gemsets.tt} +0 -0
  51. /data/lib/specimen/generator/{templates/rubocop.yml.tt → project/templates/root/.rubocop.yml.tt} +0 -0
  52. /data/lib/specimen/generator/{templates → project/templates/root}/Gemfile.tt +0 -0
  53. /data/lib/specimen/generator/{templates → project/templates/root}/README.md.tt +0 -0
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'specimen/command/base'
4
+
5
+ module Specimen
6
+ module Command
7
+ class GemHelpCommand < Base
8
+ def self.source_root
9
+ File.dirname(__FILE__)
10
+ end
11
+
12
+ no_commands do
13
+ def perform
14
+ if class_usage
15
+ say class_usage
16
+ else
17
+ help
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ Usage:
2
+ specimen init --name=NAME [options] # Create a new specimen project which will generate following dirs and files
3
+
4
+ * default directories /config, /lib, /tmp
5
+ * default directories (/features/..) and files for cucumber unless --skip-cucumber
6
+ * default directories (/spec/..) and files for RSpec unless --skip-rspec
7
+ * root path files:
8
+ * .gemrc
9
+ * .gitignore
10
+ * .rspec (unless --skip-rspec)
11
+ * .rubocop.yml
12
+ * cucumber.yml (unless --skip-cucumber)
13
+ * Gemfile (based on the used options)
14
+ * README.md
15
+ * specimen.yml (default configuration file for the specimen gem
16
+
17
+ Options:
18
+ -n, [--name=NAME] # required: true
19
+ The name of your project and installation path relative to your current PWD
20
+
21
+ [--ui-driver=UI_DRIVER] # Default: 'watir'
22
+ Valid options are:
23
+ * watir
24
+ * selenium
25
+ * selenium-webdriver
26
+
27
+ [--skip-ui] # Default: false
28
+ Don´t add selenium or watir gem to your Gemfile
29
+
30
+ [--skip-cucumber] # Default: false
31
+ No cucumber gems will be added to your Gemfile.
32
+ Creation of cucumber directories and files will be skipped.
33
+
34
+ [--skip-rspec] # Default: false
35
+ RSpec will still be in your Gemfile (as it´s matchers are used for cucumber)
36
+ but no RSpec directories and files will be generated
37
+
38
+ -h, [--help] # You are looking at it.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'specimen/generator'
4
+
5
+ module Specimen
6
+ module Command
7
+ class InitCommand < BaseGroup
8
+ class RequiredOptionError < StandardError
9
+ def initialize
10
+ @msg = "Please provide required option '--name'!"
11
+ super(@msg)
12
+ end
13
+ end
14
+
15
+ class ProjectNameError < StandardError
16
+ def initialize
17
+ @msg = "Option '--name' can not be empty or equal 'name'!"
18
+ super(@msg)
19
+ end
20
+ end
21
+
22
+ def self.source_root
23
+ File.dirname(__FILE__)
24
+ end
25
+
26
+ namespace :init
27
+
28
+ class_option :name, aliases: %w[-n], type: :string
29
+ class_option :skip_ui, type: :boolean, default: false
30
+ class_option :ui_driver, type: :string, default: ''
31
+ class_option :skip_cucumber, type: :boolean, default: false
32
+ class_option :skip_rspec, type: :boolean, default: false
33
+
34
+ no_commands do
35
+ def perform
36
+ run_options_checks!
37
+
38
+ Generator::SpecimenProjectGenerator.new(args, options).perform
39
+ end
40
+
41
+ def run_options_checks!
42
+ raise RequiredOptionError if options[:name].nil?
43
+ raise RequiredOptionError if options[:name].empty?
44
+ raise ProjectNameError if options[:name] == 'name'
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Command
5
+ class SpecsCommand < SpecsRunner
6
+ namespace :specs
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Copied without any regrets from activesupport or rails :) to make the 'deep_symbolize_keys' methods available
3
4
  class Hash
4
5
  # Returns a new hash with all keys converted to strings.
5
6
  #
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class SpecimenProjectConfig
6
+ class InvalidUIDriverError < RuntimeError
7
+ def initialize(driver)
8
+ @msg = "Invalid UI driver name '#{driver}'. Set --ui-driver to\n * #{VALID_UI_DRIVER_NAMES.join("\n * ")}"
9
+ super(@msg)
10
+ end
11
+ end
12
+
13
+ GEM_LIST = %w[
14
+ activesupport dotenv ffaker rest-client thor uuid
15
+ cucumber cuke_modeler parallel_tests rspec
16
+ selenium-webdriver watir
17
+ debug pry rubocop
18
+ ].freeze
19
+
20
+ VALID_UI_DRIVER_NAMES = %w[selenium selenium-webdriver watir].freeze
21
+
22
+ def self.parse(options)
23
+ new(options).parse_options
24
+ end
25
+
26
+ def initialize(options)
27
+ @options = options
28
+ end
29
+
30
+ def parse_options
31
+ run_ui_driver_check!
32
+ config
33
+ end
34
+
35
+ def config
36
+ @config ||= {
37
+ project_name: project_name,
38
+ root_path: "#{Specimen.init_wd_path.to_path}/#{project_name}",
39
+ gems: project_gems,
40
+ skip_ui: skip_ui?,
41
+ cucumber: !skip_cucumber?,
42
+ rspec: !skip_rspec?,
43
+ ui_driver: ui_driver
44
+ }
45
+ end
46
+
47
+ def run_ui_driver_check!
48
+ return if skip_ui?
49
+
50
+ raise InvalidUIDriverError, ui_driver unless VALID_UI_DRIVER_NAMES.include?(ui_driver)
51
+ end
52
+
53
+ def project_gems
54
+ reject_cucumber_gems if skip_cucumber?
55
+ reject_all_ui_driver_gems if skip_ui?
56
+ reject_ui_driver_gem
57
+
58
+ gems.sort
59
+ end
60
+
61
+ def reject_cucumber_gems
62
+ gems.reject! { |gem| gem.eql?('cucumber') || gem.eql?('cuke_modeler') }
63
+ end
64
+
65
+ def reject_all_ui_driver_gems
66
+ gems.reject! { |gem| gem.eql?('selenium-webdriver') || gem.eql?('watir') }
67
+ end
68
+
69
+ def reject_ui_driver_gem
70
+ reject_watir_gem
71
+ reject_selenium_gem
72
+ end
73
+
74
+ def reject_watir_gem
75
+ gems.reject! { |gem| gem.eql?('watir') } if selenium?
76
+ end
77
+
78
+ def reject_selenium_gem
79
+ gems.reject! { |gem| gem.eql?('selenium-webdriver') } if watir?
80
+ end
81
+
82
+ def selenium?
83
+ ui_driver == 'selenium' || ui_driver == 'selenium-webdriver'
84
+ end
85
+
86
+ def watir?
87
+ ui_driver == 'watir'
88
+ end
89
+
90
+ def ui_driver?
91
+ !@options[:ui_driver].empty?
92
+ end
93
+
94
+ def ui_driver
95
+ return nil if skip_ui?
96
+ return @options[:ui_driver] if ui_driver?
97
+
98
+ 'watir'
99
+ end
100
+
101
+ def project_name
102
+ @options[:name]
103
+ end
104
+
105
+ def gems
106
+ @gems ||= GEM_LIST.dup
107
+ end
108
+
109
+ def skip_ui?
110
+ @options[:skip_ui]
111
+ end
112
+
113
+ def skip_cucumber?
114
+ @options[:skip_cucumber]
115
+ end
116
+
117
+ def skip_rspec?
118
+ @options[:skip_rspec]
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class CucumberProjectGenerator < ProjectGeneratorBase
6
+ DEFAULT_DIRECTORIES = %w[features/examples features/step_definitions/examples features/support].freeze
7
+ TEMPLATES_DIR = 'cucumber/templates'
8
+ TEMPLATES = %w[
9
+ features/examples/add_numbers.feature
10
+ features/support/env.rb
11
+ config/cucumber.yml
12
+ ].freeze
13
+
14
+ def execute!
15
+ perform
16
+ end
17
+
18
+ no_commands do
19
+ def perform
20
+ create_directories(DEFAULT_DIRECTORIES)
21
+ create_files_by_templates(TEMPLATES_DIR, TEMPLATES)
22
+
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1 @@
1
+ default: --require features/support/env.rb
@@ -0,0 +1,9 @@
1
+ Feature: Add numbers example feature
2
+
3
+ Scenario:
4
+ Given I add 1 and 1
5
+ Then the result should be 2
6
+
7
+ Scenario:
8
+ Given I add 1 and 3
9
+ Then the result should be 3
@@ -2,17 +2,19 @@
2
2
 
3
3
  module Specimen
4
4
  module Generator
5
- class FileByTemplate < Commands::BaseGroupCommand
6
- argument :template_file, type: :string
5
+ # Do not move this class to not screw up the template lookup path!
6
+ class FileByTemplate < GeneratorBase
7
+ argument :template_path, type: :string
7
8
  argument :destination, type: :string
8
9
  argument :data, type: :hash, default: {}
9
10
 
10
11
  def self.source_root
11
- "#{File.dirname(__FILE__)}/templates"
12
+ File.dirname(__FILE__)
12
13
  end
13
14
 
15
+ # Example template_path => 'project/templates/root/.gemrc'
14
16
  def create
15
- template(template_file, destination, data)
17
+ template(template_path, destination, data)
16
18
  end
17
19
  end
18
20
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class GeneratorBase < Command::BaseGroup
6
+ include Generator
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class ProjectRootGenerator < ProjectGeneratorBase
6
+ DEFAULT_DIRECTORIES = %w[config lib tmp].freeze
7
+ TEMPLATES_DIR = 'project/templates/root'
8
+ TEMPLATES = %w[
9
+ .gemrc
10
+ .gitignore
11
+ .rubocop.yml
12
+ Gemfile
13
+ README.md
14
+ config/specimen.yml
15
+ ].freeze
16
+
17
+ # argument :config
18
+
19
+ def execute!
20
+ perform
21
+ end
22
+
23
+ no_commands do
24
+ def perform
25
+ create_directories(DEFAULT_DIRECTORIES)
26
+ create_files_by_templates(TEMPLATES_DIR, TEMPLATES)
27
+
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class SpecimenProjectGenerator < GeneratorBase
6
+ def execute!
7
+ perform
8
+ end
9
+
10
+ no_commands do
11
+ def perform
12
+ ProjectRootGenerator.start([config])
13
+ CucumberProjectGenerator.start([config]) if config[:cucumber]
14
+ RSpecProjectGenerator.start([config]) if config[:rspec]
15
+
16
+ true
17
+ end
18
+
19
+ def config
20
+ @config ||= Generator::SpecimenProjectConfig.parse(options)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ *.idea/
2
+ /.bundle
3
+
4
+ tmp/
@@ -0,0 +1,38 @@
1
+ default: &default
2
+ threads: 1
3
+ env:
4
+ - FOO='123'
5
+
6
+ cucumber: &cucumber
7
+ framework: cucumber
8
+ profiles: []
9
+ options: &options
10
+ - --publish-quiet
11
+ - --format pretty
12
+ - --format html --out tmp/cucumber_result.html
13
+ env:
14
+ - FOO='123'
15
+ - BAZ='something'
16
+
17
+ rspec: &rspec
18
+ framework: rspec
19
+ options:
20
+ - --options config/.rspec
21
+ - --format progress
22
+ - --format documentation
23
+ - --format html --out tmp/rspec_result.html
24
+ env:
25
+ - FOO='123'
26
+ - BAZ='something'
27
+
28
+ ci_specs:
29
+ <<: *rspec
30
+ env:
31
+ - CI='1'
32
+
33
+ ci_cukes:
34
+ <<: *cucumber
35
+ env:
36
+ - CI='1'
37
+ profiles:
38
+ - regression
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class ProjectGeneratorBase < GeneratorBase
6
+ argument :config
7
+
8
+ no_commands do
9
+ def root_path
10
+ @root_path ||= config[:root_path]
11
+ end
12
+
13
+ def create_directories(dirs, destination_path = root_path)
14
+ empty_directory(destination_path)
15
+
16
+ inside destination_path do
17
+ dirs.each { |dir| empty_directory(dir) }
18
+ end
19
+ end
20
+
21
+ def create_files_by_templates(templates_dir, templates = [], destination_path = root_path)
22
+ template_paths = templates.map { |template| "#{templates_dir}/#{template}" }
23
+
24
+ template_paths.each do |template|
25
+ file_destination = template.gsub(templates_dir, '').delete_prefix('/')
26
+ file_destination_path = "#{destination_path}/#{file_destination}"
27
+
28
+ create_file_by_template(template, file_destination_path, config)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specimen
4
+ module Generator
5
+ class RSpecProjectGenerator < ProjectGeneratorBase
6
+ DEFAULT_DIRECTORIES = %w[spec/examples spec/support].freeze
7
+ TEMPLATES_DIR = 'rspec/templates'
8
+ TEMPLATES = %w[
9
+ spec/examples/example_spec.rb
10
+ spec/spec_helper.rb
11
+ config/.rspec
12
+ ].freeze
13
+
14
+ def execute!
15
+ perform
16
+ end
17
+
18
+ no_commands do
19
+ def perform
20
+ create_directories(DEFAULT_DIRECTORIES)
21
+ create_files_by_templates(TEMPLATES_DIR, TEMPLATES)
22
+
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ --require ./spec/spec_helper
2
+
3
+ --format progress
4
+ --format documentation
5
+ --format html --out tmp/rspec_result.html
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'Example specs' do
4
+ context 'run example tests' do
5
+ before(:example) do
6
+ @foo = '123'
7
+ end
8
+
9
+ it 'passes' do
10
+ expect(@foo).to eq '123'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require rubygems
4
+ require 'pry'
5
+
6
+ RSpec.configure do |config|
7
+ # generic base config for each example
8
+ config.before(:example) do
9
+ p 'always running'
10
+ end
11
+ end
12
+
13
+ at_exit do
14
+ p 'bye bye'
15
+ end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'generator/generator_base'
4
+ require_relative 'generator/project_generator_base'
3
5
  require_relative 'generator/file_by_template'
4
- require_relative 'generator/specimen_project'
6
+
7
+ require_relative 'generator/configs/specimen_project_config'
8
+ require_relative 'generator/cucumber/cucumber_project_generator'
9
+ require_relative 'generator/project/project_root_generator'
10
+ require_relative 'generator/project/specimen_project_generator'
11
+ require_relative 'generator/rspec/rspec_project_generator'
5
12
 
6
13
  module Specimen
7
14
  module Generator
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'psych'
4
+
5
+ module Specimen
6
+ module Runtime
7
+ class YmlParser
8
+ class YmlTypeError < StandardError; end
9
+ class YmlERBError < StandardError; end
10
+ class YmlDataError < StandardError; end
11
+
12
+ def self.parse!(file)
13
+ new(file).parse
14
+ end
15
+
16
+ def initialize(file)
17
+ @file = file
18
+ end
19
+
20
+ def parse
21
+ specimen_yml_data
22
+ end
23
+
24
+ private
25
+
26
+ def specimen_yml
27
+ @specimen_yml ||= File.read(@file)
28
+ end
29
+
30
+ def specimen_yml_erb
31
+ @specimen_yml_erb ||= ERB.new(specimen_yml, trim_mode: '%').result(binding)
32
+ rescue StandardError
33
+ raise YmlERBError, "#{@file} could not be parsed with ERB!"
34
+ end
35
+
36
+ def specimen_yml_data
37
+ return @specimen_yml_data if @specimen_yml_data
38
+
39
+ data = Psych.load(specimen_yml_erb, aliases: true)
40
+
41
+ raise YmlTypeError, 'specimen.yml data could not be parsed' unless data.is_a?(Hash)
42
+
43
+ @specimen_yml_data = data
44
+ rescue StandardError
45
+ raise YmlDataError, "Could not read data from: '#{@file}'!"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runtime/yml_parser'
4
+
5
+ module Specimen
6
+ module Runtime
7
+ class MissingCommandOptionError < RuntimeError
8
+ def initialize(command, option)
9
+ @msg = "Command '#{command}' misses option: '#{option}'"
10
+ super(@msg)
11
+ end
12
+ end
13
+
14
+ class UndefinedYmlError < RuntimeError
15
+ def initialize
16
+ @msg = "Option '--config-fie' is not set!"
17
+ super(@msg)
18
+ end
19
+ end
20
+
21
+ class YmlNotFoundError < RuntimeError
22
+ def initialize(yml_path)
23
+ @msg = "No such file: '#{yml_path.to_path}'"
24
+ super(@msg)
25
+ end
26
+ end
27
+
28
+ DEFAULT_YML_NAME = 'specimen.yml'
29
+
30
+ class << self
31
+ attr_reader :command, :config
32
+
33
+ def start!(command, **config)
34
+ @command = command
35
+ @config = config
36
+
37
+ return if skip_yml_load?
38
+ raise YmlNotFoundError, yml_path unless yml_path.exist?
39
+
40
+ self
41
+ end
42
+
43
+ def work_dir
44
+ @work_dir ||= Pathname.getwd
45
+ end
46
+
47
+ def config_dir
48
+ @config_dir ||= Pathname.new("#{work_dir}/config")
49
+ end
50
+
51
+ def yml_path
52
+ @yml_path ||= Pathname.new("#{config_dir}/#{yml_name}")
53
+ end
54
+
55
+ def skip_yml_load?
56
+ command.is_a?(Command::InitCommand)
57
+ end
58
+
59
+ def yml_name
60
+ return @yml_name if @yml_name
61
+
62
+ key_name = 'config_file'
63
+ raise MissingCommandOptionError, command, key_name unless command.options.key?(key_name)
64
+
65
+ yml_name = command.options[key_name]
66
+ raise UndefinedYmlError if yml_name.empty?
67
+
68
+ @yml_name = yml_name
69
+ end
70
+
71
+ def yml_data
72
+ @yml_data ||= YmlParser.parse!(yml_path.to_path)
73
+ end
74
+
75
+ def default_yml_data
76
+ @default_yml_data ||= profile_yml_data('default')
77
+ end
78
+
79
+ def profile_yml_data(profile = nil)
80
+ yml_data[profile]
81
+ end
82
+ end
83
+ end
84
+ end