omnitest-psychic 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +36 -0
  6. data/.travis.yml +10 -0
  7. data/CONTRIBUTING.md +61 -0
  8. data/Gemfile +23 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +234 -0
  11. data/Rakefile +12 -0
  12. data/appveyor.yml +9 -0
  13. data/bin/psychic +4 -0
  14. data/docs/code_samples.md +92 -0
  15. data/docs/index.md +128 -0
  16. data/lib/omnitest/output_helper.rb +84 -0
  17. data/lib/omnitest/psychic.rb +191 -0
  18. data/lib/omnitest/psychic/cli.rb +171 -0
  19. data/lib/omnitest/psychic/code2doc.rb +75 -0
  20. data/lib/omnitest/psychic/code2doc/code_helper.rb +122 -0
  21. data/lib/omnitest/psychic/code2doc/code_segmenter.rb +170 -0
  22. data/lib/omnitest/psychic/code2doc/comment_styles.rb +92 -0
  23. data/lib/omnitest/psychic/command_template.rb +35 -0
  24. data/lib/omnitest/psychic/error.rb +15 -0
  25. data/lib/omnitest/psychic/execution/default_strategy.rb +82 -0
  26. data/lib/omnitest/psychic/execution/env_strategy.rb +12 -0
  27. data/lib/omnitest/psychic/execution/flag_strategy.rb +14 -0
  28. data/lib/omnitest/psychic/execution/token_strategy.rb +68 -0
  29. data/lib/omnitest/psychic/factories/go_factories.rb +14 -0
  30. data/lib/omnitest/psychic/factories/hot_read_task_factory.rb +54 -0
  31. data/lib/omnitest/psychic/factories/java_factories.rb +81 -0
  32. data/lib/omnitest/psychic/factories/powershell_factories.rb +53 -0
  33. data/lib/omnitest/psychic/factories/ruby_factories.rb +56 -0
  34. data/lib/omnitest/psychic/factories/shell_factories.rb +89 -0
  35. data/lib/omnitest/psychic/factories/travis_factories.rb +33 -0
  36. data/lib/omnitest/psychic/factory_manager.rb +46 -0
  37. data/lib/omnitest/psychic/file_finder.rb +69 -0
  38. data/lib/omnitest/psychic/hints.rb +21 -0
  39. data/lib/omnitest/psychic/magic_task_factory.rb +98 -0
  40. data/lib/omnitest/psychic/script.rb +146 -0
  41. data/lib/omnitest/psychic/script_factory.rb +66 -0
  42. data/lib/omnitest/psychic/script_factory_manager.rb +24 -0
  43. data/lib/omnitest/psychic/script_runner.rb +45 -0
  44. data/lib/omnitest/psychic/task.rb +6 -0
  45. data/lib/omnitest/psychic/task_factory_manager.rb +19 -0
  46. data/lib/omnitest/psychic/task_runner.rb +30 -0
  47. data/lib/omnitest/psychic/tokens.rb +51 -0
  48. data/lib/omnitest/psychic/version.rb +5 -0
  49. data/lib/omnitest/psychic/workflow.rb +27 -0
  50. data/lib/omnitest/shell.rb +27 -0
  51. data/lib/omnitest/shell/buff_shellout_executor.rb +41 -0
  52. data/lib/omnitest/shell/execution_result.rb +61 -0
  53. data/lib/omnitest/shell/mixlib_shellout_executor.rb +66 -0
  54. data/mkdocs.yml +6 -0
  55. data/omnitest-psychic.gemspec +36 -0
  56. data/spec/fabricators/shell_fabricator.rb +9 -0
  57. data/spec/omnitest/psychic/code2doc/code_helper_spec.rb +123 -0
  58. data/spec/omnitest/psychic/execution/default_strategy_spec.rb +17 -0
  59. data/spec/omnitest/psychic/execution/env_strategy_spec.rb +17 -0
  60. data/spec/omnitest/psychic/execution/flag_strategy_spec.rb +26 -0
  61. data/spec/omnitest/psychic/execution/token_strategy_spec.rb +26 -0
  62. data/spec/omnitest/psychic/factories/java_factories_spec.rb +35 -0
  63. data/spec/omnitest/psychic/factories/powershell_factories_spec.rb +59 -0
  64. data/spec/omnitest/psychic/factories/ruby_factories_spec.rb +91 -0
  65. data/spec/omnitest/psychic/factories/shell_factories_spec.rb +79 -0
  66. data/spec/omnitest/psychic/factories/travis_factories_spec.rb +78 -0
  67. data/spec/omnitest/psychic/hot_read_task_factory_spec.rb +51 -0
  68. data/spec/omnitest/psychic/script_factory_manager_spec.rb +57 -0
  69. data/spec/omnitest/psychic/script_spec.rb +55 -0
  70. data/spec/omnitest/psychic/shell_spec.rb +68 -0
  71. data/spec/omnitest/psychic/workflow_spec.rb +46 -0
  72. data/spec/omnitest/psychic_spec.rb +170 -0
  73. data/spec/spec_helper.rb +52 -0
  74. metadata +352 -0
@@ -0,0 +1,53 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module Factories
4
+ class PowerShellTaskFactory < MagicTaskFactory
5
+ TASK_PRIORITY = 1
6
+ EXTENSIONS = ['.ps1']
7
+ magic_file 'scripts/*.ps1'
8
+ register_task_factory
9
+
10
+ def initialize(psychic, opts = {})
11
+ super
12
+ @known_tasks = Dir["#{cwd}/scripts/*"].map do | script |
13
+ File.basename(script, File.extname(script)) if EXTENSIONS.include?(File.extname(script))
14
+ end
15
+ end
16
+
17
+ def task(task_alias)
18
+ task = task_alias.to_s
19
+ script = Dir["#{cwd}/scripts/#{task}{.ps1}"].first
20
+ relativize_cmd(script) if script
21
+ end
22
+
23
+ def active?
24
+ true if psychic.os_family == :windows
25
+ end
26
+
27
+ private
28
+
29
+ def relativize_cmd(cmd)
30
+ cmd = Omnitest::Core::FileSystem.relativize(cmd, cwd)
31
+ "PowerShell -NoProfile -ExecutionPolicy Bypass -File \"#{cmd}\""
32
+ end
33
+ end
34
+
35
+ class PowerShellScriptFactory < ScriptFactory
36
+ runs '**.ps1', 5
37
+
38
+ def active?
39
+ true if psychic.os_family == :windows
40
+ end
41
+
42
+ def script(script)
43
+ script = psychic.task('run_script')
44
+ if script
45
+ "#{script} #{script.source_file}"
46
+ else
47
+ relativize_cmd(script.absolute_source_file)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,56 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module Factories
4
+ module UsesBundler
5
+ def bundle_command
6
+ bundler_active? ? 'bundle exec ' : ''
7
+ end
8
+
9
+ protected
10
+
11
+ def bundler_active?
12
+ psychic.task_factory_manager.active? BundlerTaskFactory
13
+ end
14
+ end
15
+
16
+ class BundlerTaskFactory < MagicTaskFactory
17
+ TASK_PRIORITY = 2
18
+ magic_file 'Gemfile'
19
+ magic_file '.bundle/config'
20
+ magic_env_var 'BUNDLE_GEMFILE'
21
+ register_task_factory
22
+
23
+ def active?
24
+ # Avoid detecting omnitest's own BUNDLE_GEMFILE variable
25
+ Bundler.with_clean_env do
26
+ super
27
+ end
28
+ end
29
+
30
+ task :bootstrap do
31
+ 'bundle install'
32
+ end
33
+ end
34
+
35
+ class RakeFactory < MagicTaskFactory
36
+ include UsesBundler
37
+ magic_file 'Rakefile'
38
+ register_task_factory
39
+
40
+ task :test do
41
+ [bundle_command, 'rake'].join
42
+ end
43
+ end
44
+
45
+ class RubyFactory < ScriptFactory
46
+ include UsesBundler
47
+ register_script_factory
48
+ runs '**.rb', 8
49
+
50
+ def script(script)
51
+ [bundle_command, "ruby #{script.source_file}"].join
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,89 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module Factories
4
+ module ShellBase
5
+ EXTENSIONS = ['.sh', '']
6
+
7
+ def active?
8
+ true unless psychic.os_family == :windows
9
+ end
10
+
11
+ protected
12
+
13
+ def relativize_cmd(cmd)
14
+ cmd = Omnitest::Core::FileSystem.relativize(cmd, cwd)
15
+ "./#{cmd}" unless cmd.to_s.start_with? '/'
16
+ end
17
+ end
18
+
19
+ class ShellTaskFactory < MagicTaskFactory
20
+ include ShellBase
21
+ TASK_PRIORITY = 1
22
+ magic_file '{script,scripts}/*'
23
+ register_task_factory
24
+
25
+ def initialize(*args)
26
+ super
27
+ @known_tasks = Dir.glob("#{cwd}/{script,scripts}/*", File::FNM_CASEFOLD).map do | script |
28
+ File.basename(script, File.extname(script)) if EXTENSIONS.include?(File.extname(script))
29
+ end
30
+ end
31
+
32
+ def task(task_alias)
33
+ task = task_alias.to_s
34
+ script = Dir.glob("#{cwd}/{script,scripts}/#{task}{.sh,}", File::FNM_CASEFOLD).first
35
+ relativize_cmd(script) if script
36
+ end
37
+ end
38
+
39
+ class ShellScriptFactory < ScriptFactory
40
+ include ShellBase
41
+ register_script_factory
42
+ runs '**.sh', 5
43
+
44
+ def shell_task_factory
45
+ psychic.task_factory_manager.active? ShellTaskFactory
46
+ end
47
+
48
+ def priority_for_script(script)
49
+ return 8 if shell_task_factory.known_task? :run_script
50
+
51
+ case script.extname
52
+ when '.sh'
53
+ 9
54
+ when ''
55
+ 7
56
+ else
57
+ 5 if shebang?(script)
58
+ end
59
+ end
60
+
61
+ def script(script)
62
+ base_command = run_script_command
63
+ if base_command
64
+ "#{base_command} #{script.source_file}"
65
+ else
66
+ relativize_cmd(script.absolute_source_file)
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def run_script_command
73
+ psychic.task('run_script')
74
+ rescue TaskNotImplementedError
75
+ nil
76
+ end
77
+
78
+ def shebang?(script)
79
+ first_line = script.source.lines.first
80
+ first_line && first_line.match(/\#\!/)
81
+ rescue => e
82
+ logger.warn("Could not read #{script.source_file}: #{e.message}")
83
+ # Could be binary, unknown encoding, ...
84
+ false
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module Factories
4
+ class TravisTaskFactory < MagicTaskFactory
5
+ TASK_PRIORITY = 2
6
+ magic_file '.travis.yml'
7
+ register_task_factory
8
+
9
+ def active?
10
+ super && travis_allowed? && travis_build_installed?
11
+ end
12
+
13
+ def travis_allowed?
14
+ psychic.opts[:travis]
15
+ end
16
+
17
+ def travis_build_installed?
18
+ # check that the travis-build extension is installed
19
+ # HACK: use the MixlibShellOutExecutor
20
+ Bundler.with_clean_env { `travis help --skip-version-check`.match(/run/) }
21
+ end
22
+
23
+ task :bootstrap do
24
+ psychic.execute('travis run --print --skip-version-check install').stdout
25
+ end
26
+
27
+ task :unittest do
28
+ psychic.execute('travis run --print --skip-version-check script').stdout
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class FactoryManager
4
+ include Omnitest::Core::Logger
5
+
6
+ BUILT_IN_DIR = File.expand_path('../factories', __FILE__)
7
+
8
+ class << self
9
+ def autoload_factories!
10
+ # Load built-in task factories
11
+ Dir.glob("#{BUILT_IN_DIR}/*.rb", File::FNM_CASEFOLD).each do |task_factory_file|
12
+ require task_factory_file
13
+ end
14
+ end
15
+
16
+ def factory_classes
17
+ @factory_classes ||= Set.new
18
+ end
19
+
20
+ def register_factory(klass)
21
+ factory_classes.add klass
22
+ end
23
+
24
+ def clear
25
+ factory_classes.clear
26
+ end
27
+ end
28
+
29
+ attr_reader :factories
30
+
31
+ def initialize(*args)
32
+ @factories = self.class.factory_classes.map { |k| k.new(*args) }
33
+ end
34
+
35
+ def active_factories
36
+ factories.select(&:active?).sort_by(&:priority)
37
+ end
38
+
39
+ def active?(klass)
40
+ factories.find do | factory |
41
+ factory.is_a? klass
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class FileFinder
4
+ attr_reader :search_path, :ignored_patterns
5
+
6
+ def initialize(search_path, ignored_patterns)
7
+ @search_path = search_path
8
+ @ignored_patterns = ignored_patterns || read_gitignore(search_path)
9
+ end
10
+
11
+ # Finds a file by loosely matching the file name to a scenario name
12
+ def find_file(name)
13
+ return name if File.exist? File.expand_path(name, search_path)
14
+
15
+ # Filter out ignored files
16
+ files = potential_files(name).select do |f|
17
+ !ignored?(f)
18
+ end
19
+
20
+ if block_given?
21
+ file = yield files
22
+ else
23
+ # Select the shortest path, likely the best match
24
+ file = files.min_by(&:length)
25
+ end
26
+
27
+ fail Errno::ENOENT, "No file was found for #{name} within #{search_path}" if file.nil?
28
+ Omnitest::Core::FileSystem.relativize(file, search_path)
29
+ end
30
+
31
+ def potential_files(name)
32
+ slugified_name = Omnitest::Core::FileSystem.slugify(name)
33
+ glob_string = "#{search_path}/**/*#{slugified_name}*.*"
34
+ potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
35
+ potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
36
+ potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
37
+ end
38
+
39
+ private
40
+
41
+ # @api private
42
+ def read_gitignore(dir)
43
+ gitignore_file = "#{dir}/.gitignore"
44
+ File.read(gitignore_file)
45
+ rescue
46
+ ''
47
+ end
48
+
49
+ # @api private
50
+ def ignored?(target_file)
51
+ # Trying to match the git ignore rules but there's some discrepencies.
52
+ ignored_patterns.split.find do |pattern|
53
+ # if git ignores a folder, we should ignore all files it contains
54
+ pattern = "#{pattern}**" if pattern[-1] == '/'
55
+ started_with_slash = pattern.start_with? '/'
56
+
57
+ pattern.gsub!(%r{\A/}, '') # remove leading slashes since we're searching from root
58
+ file = Omnitest::Core::FileSystem.relativize(target_file, search_path)
59
+ ignored = file.fnmatch? pattern
60
+ ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
61
+ end
62
+ end
63
+
64
+ def self.find_file_by_alias(file_alias, search_path, ignored_patterns = nil, &block)
65
+ FileFinder.new(search_path, ignored_patterns).find_file(file_alias, &block)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,21 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class Hints < Omnitest::Core::Dash
4
+ field :options, Hash, default: {}
5
+ field :tasks, Hash[String => String]
6
+ field :scripts, Hash[String => Pathname]
7
+
8
+ def options
9
+ self[:options] ||= {}
10
+ end
11
+
12
+ def tasks
13
+ self[:tasks] ||= {}
14
+ end
15
+
16
+ def scripts
17
+ self[:scripts] ||= {}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,98 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class MagicTaskFactory
4
+ include Omnitest::Core::Logger
5
+ extend Forwardable
6
+ def_delegators :psychic, :cwd, :env, :logger
7
+
8
+ TASK_PRIORITY = 5
9
+
10
+ attr_reader :known_tasks, :tasks, :hints, :priority, :psychic
11
+
12
+ class << self
13
+ def register_task_factory
14
+ Omnitest::Psychic::TaskFactoryManager.register_factory(self)
15
+ end
16
+
17
+ def magic_file_patterns
18
+ @magic_file_patterns ||= []
19
+ end
20
+
21
+ def magic_file(pattern) # rubocop:disable Style/TrivialAccessors
22
+ magic_file_patterns << pattern
23
+ end
24
+
25
+ def magic_env_vars
26
+ @magic_env_vars ||= []
27
+ end
28
+
29
+ def magic_env_var(var)
30
+ magic_env_vars << var
31
+ end
32
+
33
+ def known_tasks
34
+ @known_tasks ||= []
35
+ end
36
+
37
+ def tasks
38
+ @tasks ||= {}
39
+ end
40
+
41
+ def task(name, &block)
42
+ name = name.to_s
43
+ tasks[name] = block
44
+ known_tasks << name
45
+ end
46
+ end
47
+
48
+ def initialize(psychic, opts = {})
49
+ @psychic = psychic
50
+ @opts = opts
51
+ @priority = self.class::TASK_PRIORITY
52
+ init_attr(:known_tasks) { self.class.known_tasks }
53
+ init_attr(:tasks) { self.class.tasks }
54
+ end
55
+
56
+ def task(task_alias, *_args)
57
+ task_alias = task_alias.to_s
58
+ task = tasks[task_alias] if tasks.include? task_alias
59
+ task = instance_eval(&task) if task.respond_to? :call
60
+ fail Omnitest::Psychic::TaskNotImplementedError, task_alias if task.nil?
61
+ task
62
+ end
63
+
64
+ def active?
65
+ self.class.magic_file_patterns.each do | pattern |
66
+ return true unless Dir.glob("#{cwd}/#{pattern}", File::FNM_CASEFOLD).empty?
67
+ end
68
+ self.class.magic_env_vars.each do | var |
69
+ return true if ENV[var]
70
+ end
71
+ false
72
+ end
73
+
74
+ def known_task?(task_alias)
75
+ known_tasks.include?(task_alias.to_s)
76
+ end
77
+
78
+ def priority_for_task(task_alias)
79
+ priority if known_task? task_alias
80
+ end
81
+
82
+ private
83
+
84
+ def init_attr(var)
85
+ var_name = "@#{var}"
86
+ var_value = @opts[var]
87
+ var_value = yield if var_value.nil? && block_given?
88
+ instance_variable_set(var_name, var_value)
89
+ end
90
+
91
+ def init_attrs(*vars)
92
+ vars.each do | var |
93
+ init_attr var
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end