sfb_scripts 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|