sfb_scripts 0.1.0
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 +7 -0
- data/bin/app_up +37 -0
- data/bin/test_runner +38 -0
- data/lib/active_repo.rb +47 -0
- data/lib/bundle_manager.rb +34 -0
- data/lib/hook_manager.rb +36 -0
- data/lib/lazy_repo.rb +9 -0
- data/lib/loud_shell_runner.rb +10 -0
- data/lib/migrator.rb +28 -0
- data/lib/needs_manager.rb +85 -0
- data/lib/pre_push_hook.rb +31 -0
- data/lib/repo.rb +69 -0
- data/lib/shell_runner.rb +70 -0
- data/lib/status_checker.rb +62 -0
- data/lib/string_extension.rb +22 -0
- data/lib/test_case.rb +27 -0
- data/lib/test_collection.rb +105 -0
- data/lib/test_file_runner.rb +56 -0
- data/lib/test_method_runner.rb +53 -0
- data/lib/test_runner.rb +78 -0
- data/lib/tester.rb +70 -0
- data/lib/upper.rb +72 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7cbc870683c0380827cabc69fd8840684dc81abc
|
4
|
+
data.tar.gz: e129a948b2183ffa885bae660d5dcadfd32e0977
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c2c5d7cf13aa8a9633adf665571a9279b82fca2df14fa4360d1ba282ad5a6550ee0059f09ae3f5435efa180742884c5c3245014c8f5b64fcc83e6ea6a214e65
|
7
|
+
data.tar.gz: 8cf02cba029addbbef7fc424f4d2cf305adf20b1c5c06a0966031e34481edc8b3954ba3a5003440c441086deeb632bff7620080f755ff9c7354df7e07a04e4fe
|
data/bin/app_up
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/upper'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
#
|
9
|
+
# REBASE
|
10
|
+
#
|
11
|
+
|
12
|
+
desc "rebase", "Rebase your commits onto master. Bundle installs and migrates as needed. Will terminate if conflicts are found."
|
13
|
+
option :loud, :type => :boolean, :desc => 'Pipe output to terminal or not (output is always piped to /tmp/up.log)'
|
14
|
+
option :all, :type => :boolean, :desc => "Don't update the repo, just bundle and migrate everywhere."
|
15
|
+
|
16
|
+
def up
|
17
|
+
if options[:all]
|
18
|
+
Upper.no_git(options)
|
19
|
+
else
|
20
|
+
Upper.rebase_on_master!(options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "install_hooks", "Install a pre-push git hook that will not let you push to remote master."
|
25
|
+
def install_hooks
|
26
|
+
Upper.install_hooks(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "pre_push_hook", "Deny destructive actions to the remote master."
|
30
|
+
def pre_push_hook(git_command)
|
31
|
+
Upper.pre_push_hook(git_command, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
default_task :up
|
35
|
+
end
|
36
|
+
|
37
|
+
CLI.start(ARGV)
|
data/bin/test_runner
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/tester'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
#
|
9
|
+
# run
|
10
|
+
#
|
11
|
+
desc "status", "Find dirty tests in your git status and run them."
|
12
|
+
option :all_engines, :type => :boolean, :desc => 'Run all tests identified, regardless of engine'
|
13
|
+
option :no_selenium, :type => :boolean, :desc => 'Ignore selenium'
|
14
|
+
def status
|
15
|
+
Tester.status(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# run
|
20
|
+
#
|
21
|
+
desc "find", <<-DESC
|
22
|
+
Find tests and run them. By trying to match an individual test or the name of a test file(s).
|
23
|
+
DESC
|
24
|
+
def find(*inputs)
|
25
|
+
Tester.find(inputs, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "status_check", "Verify that you don't edit a file without editing its corresponding test file."
|
29
|
+
option :confirm_exit_status, :type => :boolean, :desc => 'Asks user whether to exit with status 0 or 1. Used in the pre-commit hook'
|
30
|
+
def status_check
|
31
|
+
Tester.status_check(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
default_task :status
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
CLI.start(ARGV)
|
data/lib/active_repo.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'repo'
|
2
|
+
|
3
|
+
class ActiveRepo < Repo
|
4
|
+
|
5
|
+
def rebase_on_master!
|
6
|
+
up do
|
7
|
+
# will raise an error with merge conflicts
|
8
|
+
begin
|
9
|
+
shell.run "git pull --rebase origin master"
|
10
|
+
rescue ShellRunner::CommandFailureError
|
11
|
+
puts "Unable to rebase. Maybe you need to stash local changes, or there are rebase conflicts"
|
12
|
+
puts `git status`
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def up_master!
|
19
|
+
move_to_master!
|
20
|
+
rebase_on_master!
|
21
|
+
end
|
22
|
+
|
23
|
+
def up
|
24
|
+
@old_sha = current_sha
|
25
|
+
yield
|
26
|
+
@new_sha = current_sha
|
27
|
+
end
|
28
|
+
|
29
|
+
def pull_origin_master!
|
30
|
+
up do
|
31
|
+
fetch_origin
|
32
|
+
reset_hard_origin_master!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch_origin
|
37
|
+
shell.run 'git fetch origin'
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset_hard_origin_master!
|
41
|
+
shell.run "git reset --hard origin/master"
|
42
|
+
end
|
43
|
+
|
44
|
+
def move_to_master!
|
45
|
+
shell.run "git checkout master"
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class BundleManager
|
2
|
+
attr_accessor :shell, :repo
|
3
|
+
|
4
|
+
def initialize(repo: raise, shell: raise)
|
5
|
+
@shell = shell
|
6
|
+
@repo = repo
|
7
|
+
end
|
8
|
+
|
9
|
+
def bundle_where_necessary
|
10
|
+
find("Gemfile.lock").each do |gemfile_lock|
|
11
|
+
if repo.changed?(gemfile_lock)
|
12
|
+
bundle(directory_of(gemfile_lock))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def bundle(gemfile_directory)
|
18
|
+
begin
|
19
|
+
shell.run "bundle install --local", dir: gemfile_directory
|
20
|
+
rescue ShellRunner::CommandFailureError
|
21
|
+
puts 'trying without --local'
|
22
|
+
shell.run "bundle install", dir: gemfile_directory
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(file_name)
|
27
|
+
Dir.glob("**/#{file_name}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def directory_of(file_path)
|
31
|
+
File.dirname(file_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/hook_manager.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class HookManager
|
2
|
+
|
3
|
+
def self.install!(env)
|
4
|
+
new(env).install!
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :shell, :repo
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@shell = env[:shell]
|
11
|
+
@repo = env[:repo]
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_installed?
|
15
|
+
File.file?('.git/hooks/pre-push')
|
16
|
+
end
|
17
|
+
|
18
|
+
def install!
|
19
|
+
return if is_installed?
|
20
|
+
|
21
|
+
create_file
|
22
|
+
make_executable
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def create_file
|
28
|
+
shell.run %{echo 'app_up pre_push_hook "$(ps -ocommand= -p $PPID)"' > .git/hooks/pre-push}
|
29
|
+
shell.run %{echo 'test_runner status_check --confirm_exit_status' > .git/hooks/pre-commit}
|
30
|
+
end
|
31
|
+
|
32
|
+
def make_executable
|
33
|
+
shell.run "chmod +x .git/hooks/pre-push"
|
34
|
+
shell.run "chmod +x .git/hooks/pre-commit"
|
35
|
+
end
|
36
|
+
end
|
data/lib/lazy_repo.rb
ADDED
data/lib/migrator.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Migrator
|
2
|
+
attr_accessor :shell, :repo
|
3
|
+
|
4
|
+
def initialize(repo: raise, shell: raise)
|
5
|
+
@shell = shell
|
6
|
+
@repo = repo
|
7
|
+
end
|
8
|
+
|
9
|
+
def migrate_where_necessary
|
10
|
+
directories_to_migrate.each do |dir|
|
11
|
+
shell.run "bundle exec rake db:create db:migrate", dir: dir
|
12
|
+
shell.run "RAILS_ENV=test bundle exec rake db:create db:migrate", dir: dir
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def directories_to_migrate
|
17
|
+
migrate_dirs = repo.files_changed.select {|f| f.match("/migrate/") }.map {|f| File.dirname(f) }.uniq
|
18
|
+
migrate_dirs.select {|d| in_rack_application?(d) };
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def in_rack_application?(migrate_dir)
|
24
|
+
root_dir = migrate_dir.gsub(/db\/migrate$/, '')
|
25
|
+
File.file?("#{root_dir}/config.ru")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative 'shell_runner'
|
2
|
+
require_relative 'loud_shell_runner'
|
3
|
+
require_relative 'repo'
|
4
|
+
require_relative 'lazy_repo'
|
5
|
+
require_relative 'active_repo'
|
6
|
+
require_relative 'migrator'
|
7
|
+
require_relative 'bundle_manager'
|
8
|
+
require_relative 'test_runner'
|
9
|
+
|
10
|
+
class NeedsManager
|
11
|
+
|
12
|
+
def self.configure(task, needs, options)
|
13
|
+
new(task, needs, options).configure
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :needs, :options, :env, :log_file
|
17
|
+
def initialize(task, needs, options)
|
18
|
+
@log_file = log_file_for(task)
|
19
|
+
@needs = needs
|
20
|
+
@options = options
|
21
|
+
@env = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure
|
25
|
+
set_working_directory
|
26
|
+
|
27
|
+
create_shell
|
28
|
+
create_repo if needs.include? :repo
|
29
|
+
create_bundler if needs.include? :bundler
|
30
|
+
create_migrator if needs.include? :migrator
|
31
|
+
create_test_runner if needs.include? :test_runner
|
32
|
+
|
33
|
+
return env
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_working_directory
|
37
|
+
@working_directory = Repo.root_dir
|
38
|
+
Dir.chdir(@working_directory)
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_shell
|
42
|
+
env[:shell] = shell_class.new(log_file, @working_directory)
|
43
|
+
end
|
44
|
+
|
45
|
+
def shell_class
|
46
|
+
if options[:loud]
|
47
|
+
LoudShellRunner
|
48
|
+
else
|
49
|
+
ShellRunner
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_repo
|
54
|
+
env[:repo] = repo_class.new(shell: env[:shell])
|
55
|
+
end
|
56
|
+
|
57
|
+
def repo_class
|
58
|
+
if options[:repo_type] == :active
|
59
|
+
ActiveRepo
|
60
|
+
elsif options[:repo_type] == :lazy
|
61
|
+
LazyRepo
|
62
|
+
else
|
63
|
+
Repo
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_bundler
|
68
|
+
env[:bundler] = BundleManager.new(shell: env[:shell], repo: env[:repo])
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_migrator
|
72
|
+
env[:migrator] = Migrator.new(shell: env[:shell], repo: env[:repo])
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_test_runner
|
76
|
+
env[:test_runner] = TestRunner.new(
|
77
|
+
shell: env[:shell],
|
78
|
+
all_engines: options[:all_engines]
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_file_for(task)
|
83
|
+
"/tmp/#{task}.log"
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class PrePushHook
|
2
|
+
|
3
|
+
def self.check(command, env)
|
4
|
+
new(command, env).check
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :shell, :repo, :push_command
|
8
|
+
def initialize(command, env)
|
9
|
+
@shell = env[:shell]
|
10
|
+
@repo = env[:repo]
|
11
|
+
@push_command = command
|
12
|
+
end
|
13
|
+
|
14
|
+
def check
|
15
|
+
if affects_master? && is_destructive?
|
16
|
+
shell.warn "[Policy] Don't force push or delete master. (Denied by pre-push hook)"
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def affects_master?
|
24
|
+
push_command.match(/master/) || (repo.current_branch == 'master')
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_destructive?
|
28
|
+
push_command.match(/-f|delete|force| :master/)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/repo.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class Repo
|
2
|
+
|
3
|
+
def self.root_dir
|
4
|
+
@root_dir ||= %x[ git rev-parse --show-toplevel ].chomp
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :shell
|
8
|
+
|
9
|
+
def initialize(shell: shell)
|
10
|
+
@shell = shell
|
11
|
+
end
|
12
|
+
|
13
|
+
def files_changed
|
14
|
+
@files_changed ||= (shell.run "git diff --name-only #{@old_sha}").split("\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def changed?(file_path)
|
18
|
+
files_changed.include? file_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_files
|
22
|
+
@all_files ||= shell.run("git ls-tree --full-tree -r HEAD --name-only").split("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_files(pattern)
|
26
|
+
shell.run("git ls-files '*#{pattern}*'").split("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def status_files
|
30
|
+
statii = shell.run("git status -s").split("\n")
|
31
|
+
r = statii.map do |status|
|
32
|
+
status.strip!
|
33
|
+
if status[0] == 'D'
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
status.split(' ').last
|
37
|
+
end
|
38
|
+
end.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def grep(regex, file_pattern: '*')
|
42
|
+
results = shell.run("git grep '#{regex}' -- '#{file_pattern}'").split("\n")
|
43
|
+
results.map do |result|
|
44
|
+
interpret_grep_result(result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_branch
|
49
|
+
shell.run 'git rev-parse --abbrev-ref HEAD'
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def interpret_grep_result(grep_result)
|
55
|
+
splits = grep_result.split(/:/)
|
56
|
+
file = splits.shift
|
57
|
+
line = splits.join(':')
|
58
|
+
|
59
|
+
{
|
60
|
+
:file => file,
|
61
|
+
:line => line,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_sha
|
66
|
+
shell.run "git rev-parse HEAD"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/shell_runner.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative 'string_extension'
|
2
|
+
|
3
|
+
class ShellRunner
|
4
|
+
CommandFailureError = Class.new(StandardError)
|
5
|
+
|
6
|
+
attr_accessor :working_directory, :log_path
|
7
|
+
|
8
|
+
def initialize(log_path, working_directory)
|
9
|
+
@working_directory = working_directory
|
10
|
+
@queue = ''
|
11
|
+
@log_path = log_path
|
12
|
+
reset_log
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(cmd, dir: working_directory)
|
16
|
+
command = "cd #{dir} && #{cmd}"
|
17
|
+
puts command
|
18
|
+
log command
|
19
|
+
%x{ set -o pipefail && #{command} 2>> #{log_path} | tee -a #{log_path} }.chomp.tap do
|
20
|
+
raise CommandFailureError, "The following command has failed: #{command}. See #{log_path} for a full log." if ($?.exitstatus != 0)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def exec(cmd, dir: working_directory)
|
25
|
+
command = "cd #{dir} && #{cmd}"
|
26
|
+
notify "\n#{command}"
|
27
|
+
Kernel.exec command
|
28
|
+
end
|
29
|
+
|
30
|
+
def enqueue(cmd, dir: working_directory)
|
31
|
+
command = "cd #{dir} && #{cmd} && cd -"
|
32
|
+
@queue += "#{command};\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
def confirm?(question)
|
36
|
+
warn "#{question} [Yn]"
|
37
|
+
answer = STDIN.gets.strip.downcase
|
38
|
+
return answer != 'n'
|
39
|
+
end
|
40
|
+
|
41
|
+
def deny?(question)
|
42
|
+
! confirm?(question)
|
43
|
+
end
|
44
|
+
|
45
|
+
def exec_queue
|
46
|
+
notify 'running: '
|
47
|
+
notify ''
|
48
|
+
notify @queue
|
49
|
+
Kernel.exec @queue
|
50
|
+
end
|
51
|
+
|
52
|
+
def warn(msg)
|
53
|
+
log msg
|
54
|
+
puts msg.red
|
55
|
+
end
|
56
|
+
|
57
|
+
def notify(msg)
|
58
|
+
log msg
|
59
|
+
puts msg.yellow
|
60
|
+
end
|
61
|
+
|
62
|
+
def log(msg)
|
63
|
+
%x{echo "#{msg}" >> #{log_path}}
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_log
|
67
|
+
%x{echo "" > #{log_path}}
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class StatusChecker
|
2
|
+
def self.report(env, confirm_exit_status)
|
3
|
+
new(env, confirm_exit_status).report
|
4
|
+
end
|
5
|
+
|
6
|
+
attr_reader :repo, :shell, :untested_files
|
7
|
+
def initialize(env, confirm_exit_status)
|
8
|
+
@repo = env[:repo]
|
9
|
+
@shell = env[:shell]
|
10
|
+
@untested_files = []
|
11
|
+
@confirm_exit_status = confirm_exit_status
|
12
|
+
end
|
13
|
+
|
14
|
+
def report
|
15
|
+
non_test_files.each do |file|
|
16
|
+
if ! test_files.include? "#{file}_test"
|
17
|
+
untested_files << full_path_by_basename(file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if untested_files.empty?
|
22
|
+
shell.notify "All ruby files are tested!"
|
23
|
+
exit 0
|
24
|
+
else
|
25
|
+
shell.warn "The following files have changed without being tested:\n\n#{untested_files.join("\n")}"
|
26
|
+
|
27
|
+
STDIN.reopen('/dev/tty')
|
28
|
+
if confirm_exit_status? && shell.confirm?("\nDo you still wish to commit?")
|
29
|
+
exit 0
|
30
|
+
else
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def test_files
|
40
|
+
file_names.select {|f| f.match(/_test$/) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def non_test_files
|
44
|
+
file_names.reject {|f| f.match(/_test$/) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_names
|
48
|
+
@file_names ||= files.select {|f| f.match(/\.rb$/)}.map {|f| File.basename(f, '.rb') }
|
49
|
+
end
|
50
|
+
|
51
|
+
def files
|
52
|
+
@files ||= repo.status_files
|
53
|
+
end
|
54
|
+
|
55
|
+
def full_path_by_basename(file)
|
56
|
+
files.select {|f| File.basename(f, '.rb') == file }.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def confirm_exit_status?
|
60
|
+
@confirm_exit_status
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class String
|
2
|
+
# colorization
|
3
|
+
def colorize(color_code)
|
4
|
+
"\e[#{color_code}m#{self}\e[0m"
|
5
|
+
end
|
6
|
+
|
7
|
+
def red
|
8
|
+
colorize(31)
|
9
|
+
end
|
10
|
+
|
11
|
+
def green
|
12
|
+
colorize(32)
|
13
|
+
end
|
14
|
+
|
15
|
+
def yellow
|
16
|
+
colorize(33)
|
17
|
+
end
|
18
|
+
|
19
|
+
def pink
|
20
|
+
colorize(35)
|
21
|
+
end
|
22
|
+
end
|
data/lib/test_case.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class TestCase
|
2
|
+
TestDirectoryError = Class.new(StandardError)
|
3
|
+
|
4
|
+
attr_reader :working_dir, :relative_path, :test_name, :full_path
|
5
|
+
|
6
|
+
def initialize(full_path: raise, test_name: '')
|
7
|
+
@test_name = test_name
|
8
|
+
@full_path = full_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def working_dir
|
12
|
+
@working_dir ||=
|
13
|
+
if full_path.match(/^(.*)test\//)
|
14
|
+
"#{full_path.match(/^(.*)test\//)[1]}"
|
15
|
+
else
|
16
|
+
raise TestDirectoryError.new("Can't find test's working directory")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def relative_path
|
21
|
+
@relative_path ||= full_path.gsub(/^#{working_dir}/, '') || raise_file_path_error
|
22
|
+
end
|
23
|
+
|
24
|
+
def raise_file_path_error
|
25
|
+
raise TestDirectoryError.new("Can't find test's relative path")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
class TestCollection
|
4
|
+
|
5
|
+
MultipleWorkingDirectoriesError = Class.new(StandardError)
|
6
|
+
|
7
|
+
def self.parse(grep_result)
|
8
|
+
new(grep_result[:file], line: grep_result[:line]).parse
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def self.from_file_path(file_path)
|
13
|
+
new(file_path).from_file_path
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :tests
|
17
|
+
|
18
|
+
def initialize(tests_data)
|
19
|
+
@tests = tests_data.map do |test_data|
|
20
|
+
create_test_case(test_data)
|
21
|
+
end.compact
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty?
|
25
|
+
tests.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def present?
|
29
|
+
! empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
tests.size
|
34
|
+
end
|
35
|
+
|
36
|
+
def first
|
37
|
+
tests.first
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](*args)
|
41
|
+
tests[*args]
|
42
|
+
end
|
43
|
+
|
44
|
+
def in_one_file?
|
45
|
+
full_paths.size == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def in_one_engine?
|
49
|
+
working_dirs.size == 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def full_paths
|
53
|
+
@full_paths ||= tests.map {|t| t.full_path }.uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
def relative_paths
|
57
|
+
@relative_paths ||= tests.map {|t| t.relative_path }.uniq
|
58
|
+
end
|
59
|
+
|
60
|
+
def working_dirs
|
61
|
+
@working_dirs ||= tests.map {|t| t.working_dir }.uniq
|
62
|
+
end
|
63
|
+
|
64
|
+
def relative_paths_in(working_directory)
|
65
|
+
tests.select {|t| t.working_dir == working_directory}.map {|t| t.relative_path }.uniq
|
66
|
+
end
|
67
|
+
|
68
|
+
def include_selenium?
|
69
|
+
! selenium_tests.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_selenium!
|
73
|
+
@tests = tests - selenium_tests
|
74
|
+
end
|
75
|
+
|
76
|
+
def selenium_tests
|
77
|
+
tests.select {|t| t.full_path.match(/selenium/)}
|
78
|
+
end
|
79
|
+
|
80
|
+
def working_dir
|
81
|
+
raise MultipleWorkingDirectoriesError.new("Can't run tests for more than one engine") unless working_dirs.size == 1
|
82
|
+
|
83
|
+
return working_dirs.first
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def find_test_name(grepped_line)
|
89
|
+
return nil unless grepped_line
|
90
|
+
grepped_line.strip.gsub(/^\s*def /, '').strip
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_test_case(test_data)
|
94
|
+
file_path = test_data[:file]
|
95
|
+
test_name = find_test_name(test_data[:line])
|
96
|
+
return nil if ! file_path.match(/_test\.rb/)
|
97
|
+
return nil if (test_name && ! test_name.match(/^test_/))
|
98
|
+
|
99
|
+
TestCase.new(
|
100
|
+
full_path: file_path,
|
101
|
+
test_name: test_name
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative 'test_collection'
|
2
|
+
|
3
|
+
class TestFileRunner
|
4
|
+
|
5
|
+
def self.find(inputs, env)
|
6
|
+
new(env, false).find(inputs)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.status(env, ignore_selenium=false)
|
10
|
+
new(env, ignore_selenium).status
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :repo, :shell, :test_runner, :tests, :ignore_selenium
|
14
|
+
def initialize(env, ignore_selenium)
|
15
|
+
@repo = env[:repo]
|
16
|
+
@shell = env[:shell]
|
17
|
+
@test_runner = env[:test_runner]
|
18
|
+
@ignore_selenium = ignore_selenium
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(inputs)
|
22
|
+
files = []
|
23
|
+
inputs.each {|input| files << repo.find_files(input).map {|f| {:file => f} } }
|
24
|
+
files.flatten!
|
25
|
+
@tests = TestCollection.new(files)
|
26
|
+
if @tests.present?
|
27
|
+
test_runner.run_files(tests)
|
28
|
+
else
|
29
|
+
shell.notify "Could not find matching test file."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def status
|
34
|
+
files = repo.status_files.map {|f| {:file => f} }
|
35
|
+
@tests = TestCollection.new(files)
|
36
|
+
|
37
|
+
if tests.include_selenium?
|
38
|
+
handle_selenium
|
39
|
+
end
|
40
|
+
|
41
|
+
if tests.empty?
|
42
|
+
shell.notify 'No tests to run'
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
test_runner.run_files(tests)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def handle_selenium
|
52
|
+
if ignore_selenium || shell.deny?("The status includes some selenium files. Do you wish to run those?")
|
53
|
+
tests.remove_selenium!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative 'test_runner'
|
2
|
+
require_relative 'shell_runner'
|
3
|
+
|
4
|
+
class TestMethodRunner
|
5
|
+
|
6
|
+
def self.run(regex, env)
|
7
|
+
new(env).run(regex)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :regex, :repo, :shell, :test_runner
|
11
|
+
def initialize(env)
|
12
|
+
@repo = env[:repo]
|
13
|
+
@shell = env[:shell]
|
14
|
+
@test_runner = env[:test_runner]
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(regex)
|
18
|
+
@regex = regex
|
19
|
+
|
20
|
+
if tests.empty?
|
21
|
+
shell.notify "Could not find matching test method."
|
22
|
+
return false
|
23
|
+
elsif tests.size == 1
|
24
|
+
test_runner.run_method(tests.first)
|
25
|
+
elsif tests.in_one_file?
|
26
|
+
shell.notify "Multiple matches in same file. Running that file."
|
27
|
+
test_runner.run_files(tests)
|
28
|
+
elsif tests.in_one_engine? && tests.full_paths.size < 4 # hack: maybe should ask here?
|
29
|
+
shell.notify "Multiple matches across files in same engine. Running those files."
|
30
|
+
test_runner.run_files(tests)
|
31
|
+
else
|
32
|
+
shell.warn 'Found too many tests:'
|
33
|
+
tests[0..10].each {|t| shell.notify "#{t.full_path}: #{t.test_name}" }
|
34
|
+
shell.notify '...'
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def tests
|
40
|
+
@test_collection ||= TestCollection.new(find_tests_by_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_tests_by_name
|
44
|
+
test_def_regex = "^\s*def .*#{regex}.*"
|
45
|
+
begin
|
46
|
+
return repo.grep(test_def_regex, file_pattern: '*_test.rb')
|
47
|
+
rescue ShellRunner::CommandFailureError
|
48
|
+
# git grep exits with 1 if no results
|
49
|
+
return []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/test_runner.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'shell_runner'
|
2
|
+
|
3
|
+
class TestRunner
|
4
|
+
|
5
|
+
attr_reader :shell, :all_engines_param
|
6
|
+
def initialize(env)
|
7
|
+
@shell = env[:shell]
|
8
|
+
@all_engines_param = env[:all_engines]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run_method(test)
|
12
|
+
test_runner = named_test_runner(test.working_dir)
|
13
|
+
|
14
|
+
shell.exec("#{test_runner} #{test.relative_path} --name=#{test.test_name}", dir: test.working_dir)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_files(tests)
|
18
|
+
begin
|
19
|
+
test_runner = test_collection_runner(tests.working_dir)
|
20
|
+
test_files = tests.relative_paths.join(' ')
|
21
|
+
|
22
|
+
shell.exec("#{test_runner} #{test_files}", dir: tests.working_dir)
|
23
|
+
rescue TestCollection::MultipleWorkingDirectoriesError => e
|
24
|
+
if run_across_engines?
|
25
|
+
run_across_engines(tests)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run_across_engines(tests)
|
33
|
+
shell.notify "\nfinding test runners"
|
34
|
+
tests.working_dirs.each do |engine_dir|
|
35
|
+
test_files = tests.relative_paths_in(engine_dir).join(' ')
|
36
|
+
test_runner = test_collection_runner(engine_dir)
|
37
|
+
|
38
|
+
shell.enqueue("#{test_runner} #{test_files}", dir: engine_dir)
|
39
|
+
end
|
40
|
+
shell.exec_queue
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_runner_type(working_dir)
|
44
|
+
if shell.run("ls bin", dir: working_dir).split("\n").include? 'testunit'
|
45
|
+
:spring
|
46
|
+
else
|
47
|
+
:ruby
|
48
|
+
end
|
49
|
+
rescue ShellRunner::CommandFailureError
|
50
|
+
:ruby
|
51
|
+
end
|
52
|
+
|
53
|
+
def named_test_runner(working_dir)
|
54
|
+
if test_runner_type(working_dir) == :spring
|
55
|
+
# hack:
|
56
|
+
# Add some options for using spring.
|
57
|
+
#
|
58
|
+
#"bin/testunit"
|
59
|
+
"ruby -I test"
|
60
|
+
else
|
61
|
+
"ruby -I test"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_collection_runner(working_dir)
|
66
|
+
if test_runner_type(working_dir) == :spring
|
67
|
+
#"bin/testunit"
|
68
|
+
%{ruby -I test -e 'ARGV.each { |file| require(Dir.pwd + "/" + file) }'}
|
69
|
+
else
|
70
|
+
%{ruby -I test -e 'ARGV.each { |file| require(Dir.pwd + "/" + file) }'}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def run_across_engines?
|
75
|
+
all_engines_param || shell.confirm?("Test files are in multiple engines. Run them all?")
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/lib/tester.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative 'needs_manager'
|
2
|
+
require_relative 'test_collection'
|
3
|
+
require_relative 'test_method_runner'
|
4
|
+
require_relative 'test_file_runner'
|
5
|
+
require_relative 'status_checker'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'pry'
|
9
|
+
|
10
|
+
class Tester
|
11
|
+
|
12
|
+
def self.needs
|
13
|
+
[:shell, :repo, :test_runner]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find(inputs, options)
|
17
|
+
env = NeedsManager.configure(:test_runner, needs, options.merge(repo_type: :info))
|
18
|
+
new(env).find(inputs)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.status(options)
|
22
|
+
env = NeedsManager.configure(:test_runner, needs, options.merge(repo_type: :info))
|
23
|
+
new(env).status(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.status_check(options)
|
27
|
+
env = NeedsManager.configure(:test_runner, (needs - [:test_runner]), options.merge(repo_type: :info))
|
28
|
+
new(env).status_check(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :env
|
32
|
+
def initialize(env)
|
33
|
+
@env = env
|
34
|
+
end
|
35
|
+
|
36
|
+
def find(inputs)
|
37
|
+
# each of these replaces this process if successful
|
38
|
+
# so no need for logic control flow
|
39
|
+
if query_might_be_method?(inputs)
|
40
|
+
TestMethodRunner.run(inputs.first, env)
|
41
|
+
end
|
42
|
+
TestFileRunner.find(inputs, env)
|
43
|
+
env[:shell].warn "Giving up :("
|
44
|
+
end
|
45
|
+
|
46
|
+
def status(options)
|
47
|
+
TestFileRunner.status(env, options[:no_selenium])
|
48
|
+
end
|
49
|
+
|
50
|
+
def status_check(options)
|
51
|
+
StatusChecker.report(env, options[:confirm_exit_status])
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def query_might_be_method?(inputs)
|
57
|
+
if inputs.any? {|input| is_file_path?(input) }
|
58
|
+
false
|
59
|
+
elsif inputs.size > 1
|
60
|
+
false
|
61
|
+
else
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_file_path?(input)
|
67
|
+
!! input.match(/\.rb/)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/upper.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'needs_manager'
|
2
|
+
require_relative 'hook_manager'
|
3
|
+
require_relative 'pre_push_hook'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
class Upper
|
9
|
+
|
10
|
+
def self.needs
|
11
|
+
[:shell, :repo, :bundler, :migrator]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.rebase_on_master!(options)
|
15
|
+
env = NeedsManager.configure(:up, needs, options.merge(repo_type: :active))
|
16
|
+
new(env).rebase_on_master!
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.up_master!(options)
|
20
|
+
env = NeedsManager.configure(:up, needs, options.merge(repo_type: :active))
|
21
|
+
new(env).up_master!
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.no_git(options)
|
25
|
+
env = NeedsManager.configure(:up, needs, options.merge(repo_type: :lazy))
|
26
|
+
new(env).no_git
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.install_hooks(options)
|
30
|
+
env = NeedsManager.configure(:up, needs, options.merge(repo_type: :lazy))
|
31
|
+
new(env).install_hooks
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.pre_push_hook(git_command, options)
|
35
|
+
env = NeedsManager.configure(:up, needs, options.merge(repo_type: :lazy))
|
36
|
+
new(env).pre_push_hook(git_command)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :env, :repo, :bundler, :migrator
|
40
|
+
|
41
|
+
def initialize(env)
|
42
|
+
@env = env
|
43
|
+
@repo = env[:repo]
|
44
|
+
@bundler = env[:bundler]
|
45
|
+
@migrator = env[:migrator]
|
46
|
+
end
|
47
|
+
|
48
|
+
def up_master!
|
49
|
+
repo.up_master!
|
50
|
+
bundler.bundle_where_necessary
|
51
|
+
migrator.migrate_where_necessary
|
52
|
+
end
|
53
|
+
|
54
|
+
def rebase_on_master!
|
55
|
+
repo.rebase_on_master!
|
56
|
+
bundler.bundle_where_necessary
|
57
|
+
migrator.migrate_where_necessary
|
58
|
+
end
|
59
|
+
|
60
|
+
def no_git
|
61
|
+
bundler.bundle_where_necessary
|
62
|
+
migrator.migrate_where_necessary
|
63
|
+
end
|
64
|
+
|
65
|
+
def install_hooks
|
66
|
+
HookManager.install!(env)
|
67
|
+
end
|
68
|
+
|
69
|
+
def pre_push_hook(git_command)
|
70
|
+
PrePushHook.check(git_command, env)
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sfb_scripts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pete Kinnecom
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.19'
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.19'
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
description: Easily update your rails app and run tests from command line
|
34
|
+
email:
|
35
|
+
- pete.kinnecom@gmail.com
|
36
|
+
executables:
|
37
|
+
- test_runner
|
38
|
+
- app_up
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- lib/active_repo.rb
|
43
|
+
- lib/bundle_manager.rb
|
44
|
+
- lib/hook_manager.rb
|
45
|
+
- lib/lazy_repo.rb
|
46
|
+
- lib/loud_shell_runner.rb
|
47
|
+
- lib/migrator.rb
|
48
|
+
- lib/needs_manager.rb
|
49
|
+
- lib/pre_push_hook.rb
|
50
|
+
- lib/repo.rb
|
51
|
+
- lib/shell_runner.rb
|
52
|
+
- lib/status_checker.rb
|
53
|
+
- lib/string_extension.rb
|
54
|
+
- lib/test_case.rb
|
55
|
+
- lib/test_collection.rb
|
56
|
+
- lib/test_file_runner.rb
|
57
|
+
- lib/test_method_runner.rb
|
58
|
+
- lib/test_runner.rb
|
59
|
+
- lib/tester.rb
|
60
|
+
- lib/upper.rb
|
61
|
+
- bin/test_runner
|
62
|
+
- bin/app_up
|
63
|
+
homepage: http://github.com/petekinnecom/dev_scripts/
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.1.11
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Easily update your rails app and run tests from command line
|
87
|
+
test_files: []
|
88
|
+
has_rdoc:
|