orc-cli 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.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ TAC (Test Automation Client)
2
+ ===
3
+
4
+ Project Introduction
5
+ ====
6
+
7
+ It is the common test automation client can handle all kinds of test suites, including yeti, bizops, and so on.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require "fileutils"
2
+ require "rake"
3
+ require "rspec/core/rake_task"
4
+
5
+ desc "build gem file"
6
+ task :build do
7
+ config_path = File.expand_path("./config")
8
+ unless Dir.exists?(config_path)
9
+ FileUtils.mkdir(config_path)
10
+ end
11
+ src = File.join(config_path, "template/commands.yml")
12
+ dest = File.join(config_path, "commands.yml")
13
+ FileUtils.cp(src, dest)
14
+ system("gem build orc-cli.gemspec -V")
15
+ end
16
+
17
+ if defined?(RSpec)
18
+ namespace :spec do
19
+ SPEC_OPTS = %w(--format documentation --colour)
20
+
21
+ desc "Run unit tests"
22
+ RSpec::Core::RakeTask.new(:unit) do |t|
23
+ t.pattern = "spec/unit/**/*_spec.rb"
24
+ t.rspec_opts = SPEC_OPTS
25
+ end
26
+ end
27
+
28
+ desc "Run tests"
29
+ task :spec => %w(spec:unit)
30
+ end
data/bin/orc ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path("../../lib", __FILE__))
4
+ require "cli"
5
+
6
+ begin
7
+ Thread.abort_on_exception = true
8
+ CTT::Cli::Runner.run(ARGV.dup)
9
+ rescue Errno::EPIPE
10
+ puts("pipe closed, exiting...")
11
+ exit(0)
12
+ rescue Interrupt
13
+ puts "\nExiting..."
14
+ exit(1)
15
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ commands:
3
+ help:
4
+ usage: help
5
+ desc: list all avaiable commands
6
+ tests:
7
+ usage: tests
8
+ desc: run default multiple test suites.
9
+ "add suite":
10
+ usage: "add suite <Test Suite Path> [alias]"
11
+ desc: "add specific test suite to orc, and one alias can be given to resolve naming conflict"
12
+ suites:
13
+ usage: "suites"
14
+ desc: "list all available test suites registered "
15
+
data/lib/cli/base.rb ADDED
@@ -0,0 +1,13 @@
1
+
2
+ module CTT::Cli
3
+ module Command
4
+ class Base
5
+
6
+ def initialize(args, runner)
7
+ @args = args.dup
8
+ @runner = runner
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,109 @@
1
+
2
+
3
+ module CTT::Cli
4
+ class ClientCollector
5
+
6
+ def initialize(command, suite, runner)
7
+ @info = {}
8
+ @suite = suite
9
+ @suites = runner.suites
10
+ @uuid = runner.uuid
11
+
12
+ @info[:suite] = @suite
13
+ @info[:command] = command
14
+ end
15
+
16
+ def post
17
+ collect
18
+
19
+ payload = @info.dup
20
+ payload[:file] = File.open(@tar_file_path, "r")
21
+ payload[:multipart] = true
22
+
23
+ #retry 3 times
24
+ 3.times do
25
+ begin
26
+ response = RestClient.post("#{RESULTS_SERVER_URL}/tac/upload", payload)
27
+ break if response.code == 200
28
+ rescue
29
+ end
30
+ end
31
+ FileUtils.rm(payload[:file].path)
32
+ end
33
+
34
+ def collect
35
+ get_os
36
+ get_test_reports
37
+ get_uuid
38
+ get_timestamp
39
+ get_hostname
40
+ get_username
41
+ get_ipaddr
42
+ end
43
+
44
+ def get_hostname
45
+ @info[:hostname] = `hostname`.strip
46
+ end
47
+
48
+ def get_username
49
+ @info[:username] = Etc.getlogin
50
+ end
51
+
52
+ def get_ipaddr
53
+ @info[:ip] = UDPSocket.open {|s| s.connect("64.233.187.99", 1); s.addr.last}
54
+ end
55
+
56
+ def get_uuid
57
+ @info[:uuid] = @uuid.to_s
58
+ end
59
+
60
+ def get_timestamp
61
+ @info[:time] = Time.now.getutc.to_i
62
+ end
63
+
64
+ def get_os
65
+ @info[:os] = RUBY_PLATFORM
66
+
67
+ case 1.size
68
+ when 4
69
+ @info[:platform] = "32bit"
70
+ when 8
71
+ @info[:platform] = "64bit"
72
+ else
73
+ @info[:platform] = nil
74
+ end
75
+ end
76
+
77
+
78
+ def get_test_reports
79
+ suite_config_path = File.absolute_path(File.join(@suites.suites["suites"][@suite], TEST_SUITE_CONFIG_FILE))
80
+ suite_config = YAML.load_file(suite_config_path)
81
+ unless suite_config["results"]
82
+ say("no results field in #{suite_config_path}. abort!", :red)
83
+ exit(1)
84
+ end
85
+ report_path = File.absolute_path(File.join(@suites.suites["suites"][@suite], suite_config["results"]))
86
+ unless File.exists?(report_path)
87
+ say("report path did not exists. abort!", :red)
88
+ exit(1)
89
+ end
90
+ @tar_file_path = zip_test_reports(report_path)
91
+ end
92
+
93
+ def zip_test_reports(reports_path)
94
+
95
+ temp_file_path = File.join(Dir.tmpdir, "reports-#{@uuid}.tgz")
96
+ pwd = Dir.pwd
97
+ Dir.chdir(File.dirname(reports_path))
98
+ `tar czf #{temp_file_path} #{File.basename(reports_path)} 2>&1`
99
+ unless $?.exitstatus == 0
100
+ say("fail to tarball test reports. abort!", :red)
101
+ FileUtils.rm(temp_file_path)
102
+ exit(1)
103
+ end
104
+ Dir.chdir(pwd)
105
+
106
+ temp_file_path
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module CTT::Cli
3
+ STATIC_COMMANDS = {"help" => {"usage" => "help",
4
+ "desc" => "list all available commands"},
5
+ "tests" => {"usage" => "tests",
6
+ "desc" => "run default multiple test suites."},
7
+ "rerun" => {"usage" => "rerun",
8
+ "desc" => "rerun failed cases for multiple test suites."},
9
+ "add suite" => {"usage" => "add suite",
10
+ "desc" => "add specific test suite to orc"},
11
+ "delete suite" => {"usage" => "delete suite",
12
+ "desc" => "list all test suites configuration"},
13
+ "suites" => {"usage" => "suites",
14
+ "desc" => "list all test suites configuration"}}
15
+
16
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module CTT::Cli::Command
3
+ class Help < Base
4
+
5
+ def run
6
+ @commands = @runner.commands
7
+ @commands.each do |command, details|
8
+ say(details["usage"], :green)
9
+ say("\t#{details["desc"]}\n")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,137 @@
1
+
2
+ module CTT::Cli::Command
3
+
4
+ class MultipleTests < Base
5
+
6
+ MAX_RERUN_TIMES = 10
7
+
8
+ include CTT::Cli
9
+
10
+ def initialize(command, args, runner)
11
+ super(args, runner)
12
+ @suites = @runner.suites
13
+ @command = command
14
+ end
15
+
16
+ def run
17
+ eval(@command)
18
+ end
19
+
20
+ def tests
21
+ say("run multiple test suites", :green)
22
+ run_tests
23
+ show_summary
24
+ end
25
+
26
+ def rerun
27
+ say("rerun failed cases for multiple test suites", :green)
28
+ rerun_tests
29
+ show_summary(true)
30
+ end
31
+
32
+ def rerun_tests
33
+ index = 1
34
+ @suites.suites["suites"].each do |name, _|
35
+ say("#{index}) start to run failed cases for test suite: #{name}\n", :yellow)
36
+ args = @args.insert(0, @command)
37
+ cmd = TestSuite.new(name, args, @runner)
38
+ cmd.run
39
+ index += 1
40
+ end
41
+ end
42
+
43
+ def run_tests
44
+ say("**** Run multiple tests ****", :yellow)
45
+ index = 1
46
+ @suites.suites["suites"].each do |name, _|
47
+ say("#{index}) start to run test suite: #{name}\n", :yellow)
48
+ cmd = TestSuite.new(name, @args, @runner)
49
+ cmd.run
50
+ index += 1
51
+ end
52
+ end
53
+
54
+ def show_summary(rerun = false)
55
+ summary = {:total => 0,
56
+ :failed => 0,
57
+ :pending => 0,
58
+ :duration => 0.0,
59
+ :failed_cases => {},
60
+ :pending_cases => {}
61
+ }
62
+ @suites.suites["suites"].each do |name, path|
63
+ suite_config_path = File.absolute_path(File.join(path, TEST_SUITE_CONFIG_FILE))
64
+ suite_config = YAML.load_file(suite_config_path)
65
+ unless suite_config["results"]
66
+ say("no results field in #{suite_config_path}. abort!", :red)
67
+ exit(1)
68
+ end
69
+
70
+ result_path = File.join(path, suite_config["results"])
71
+ if rerun
72
+ result_file = File.join(get_rerun_folder(result_path), TEST_RESULT_FILE)
73
+ else
74
+ result_file = File.join(result_path, TEST_RESULT_FILE)
75
+ end
76
+
77
+ report = TestReport.new(result_file)
78
+ report.parse
79
+
80
+ summary[:total] += report.summary[:total]
81
+ summary[:failed] += report.summary[:failed]
82
+ summary[:pending] += report.summary[:pending]
83
+ summary[:duration] += report.summary[:duration]
84
+ summary[:failed_cases][name] = report.summary[:failed_cases]
85
+ summary[:pending_cases][name] = report.summary[:pending_cases]
86
+ end
87
+
88
+ print_cases_summary(summary)
89
+ print_failed_cases(summary)
90
+
91
+ end
92
+
93
+ def print_cases_summary(summary)
94
+ say("**** Summary for multiple tests ****", :yellow)
95
+ say("\nFinished in #{format_time(summary[:duration])}")
96
+
97
+ color = :green
98
+ color = :yellow if summary[:pending] > 0
99
+ color = :red if summary[:failed] > 0
100
+ say("#{summary[:total]} examples, #{summary[:failed]} failures, #{summary[:pending]} pendings", color)
101
+ end
102
+
103
+ def print_failed_cases(summary)
104
+ if summary[:failed] > 0
105
+ say("\nFailures:")
106
+ summary[:failed_cases].each do |suite, cases|
107
+ say(" Test Suite: #{suite}", :yellow)
108
+ cases.each do |c|
109
+ say(" #{c.strip}", :red)
110
+ end
111
+ end
112
+ say("execute #{yellow("rerun")} command to run all failed cases.")
113
+ end
114
+ end
115
+
116
+ def format_time(t)
117
+ time_str = ''
118
+ time_str += (t / 3600).to_i.to_s + " hours " if t > 3600
119
+ time_str += (t % 3600 / 60).to_i.to_s + " minutes " if t > 60
120
+ time_str += (t % 60).to_f.round(2).to_s + " seconds"
121
+ time_str
122
+ end
123
+
124
+ def get_rerun_folder(result_folder)
125
+ rerun_folder = result_folder
126
+ i = MAX_RERUN_TIMES
127
+ while(i > 0)
128
+ if File.exists?(File.join(result_folder, "rerun#{i}"))
129
+ rerun_folder = File.join(result_folder, "rerun#{i}")
130
+ break
131
+ end
132
+ i -= 1
133
+ end
134
+ rerun_folder
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module CTT::Cli::Command
3
+
4
+ class RerunTests < Base
5
+
6
+ include CTT::Cli
7
+
8
+ def initialize(args, runner)
9
+ super(args, runner)
10
+ @suites = @runner.suites
11
+ end
12
+
13
+ def run
14
+ say("rerun failed cases", :green)
15
+ #run_tests
16
+ show_summary
17
+ end
18
+
19
+ def run_tests
20
+ index = 1
21
+ @suites.suites["suites"].each do |name, _|
22
+ say("#{index}) start to run test suite: #{name}\n", :yellow)
23
+ cmd = TestSuite.new(name, @args, @runner)
24
+ cmd.run
25
+ index += 1
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,97 @@
1
+
2
+ module CTT::Cli::Command
3
+
4
+ class SuitesConfig < Base
5
+
6
+ #TEST_SUITE_CONFIG_FILE = "orc.yml"
7
+
8
+ include Interactive, CTT::Cli
9
+
10
+ def initialize(command, args, runner)
11
+ super(args, runner)
12
+
13
+ pieces = command.split(" ")
14
+ pieces.insert(0, "list") if pieces.size == 1
15
+ @action, _ = pieces
16
+
17
+ @configs = @runner.configs
18
+ @suites = @runner.suites
19
+ end
20
+
21
+ def run
22
+ eval(@action)
23
+ end
24
+
25
+ def add
26
+ puts "add suite"
27
+ #location = @configs.configs["suites"][@suite]["location"]
28
+ location = ""
29
+ invalid_input = true
30
+ 3.times do
31
+ user_input = ask("test suite source directory").strip
32
+ if user_input =~ /^~/
33
+ user_input = File.expand_path(user_input)
34
+ else
35
+ user_input = File.absolute_path(user_input)
36
+ end
37
+ if Dir.exist?(user_input)
38
+ if File.exist?(File.join(user_input, TEST_SUITE_CONFIG_FILE))
39
+ invalid_input = false
40
+ location = user_input
41
+ break
42
+ else
43
+ say("the configure file: #{yellow(TEST_SUITE_CONFIG_FILE)} " +
44
+ "cannot be found under #{user_input}.")
45
+ end
46
+ else
47
+ say("the directory: #{user_input} is invalid.")
48
+ end
49
+ end
50
+
51
+ if invalid_input
52
+ say("invalid inputs for 3 times. abort!", :red)
53
+ exit(1)
54
+ end
55
+
56
+ load_test_suite_config(location)
57
+ suite_alias = @suite_config["name"]
58
+
59
+ @suites.suites["suites"][suite_alias] = location
60
+ @suites.save
61
+ say("configure suite: #{suite_alias} successfully.", :green)
62
+ end
63
+
64
+ def list
65
+ print_suites
66
+ end
67
+
68
+ def load_test_suite_config(location)
69
+ path = File.join(location, TEST_SUITE_CONFIG_FILE)
70
+ @suite_config = YAML.load_file(path)
71
+ unless @suite_config.is_a?(Hash)
72
+ say("#{path} is not valid yml file.", :red)
73
+ exit(1)
74
+ end
75
+
76
+ # validate test suite config file
77
+ validate_points = %w(name commands results)
78
+ validate_points.each do |p|
79
+ unless @suite_config.keys.include?(p)
80
+ say("field: '#{p}' is not found in config file #{path}", :red)
81
+ exit(1)
82
+ end
83
+ end
84
+ end
85
+
86
+ def print_suites
87
+ header = ["alias", "path"]
88
+ rows = []
89
+ @suites.suites["suites"].each do |suite_alias, path|
90
+ rows << [suite_alias, path]
91
+ end
92
+ table = Terminal::Table.new(:headings => header, :rows => rows)
93
+ puts table
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,188 @@
1
+
2
+ module CTT::Cli::Command
3
+
4
+ class TestSuite < Base
5
+
6
+ #USER_INPUT = "USER_INPUT"
7
+ #TEST_SUITE_CONFIG_FILE = "orc.yml"
8
+ SUPPORT_OPTIONS = {"--force" => "bypass git dirty state check"}
9
+
10
+ include Interactive, CTT::Cli
11
+
12
+ def initialize(command, args, runner)
13
+ super(args, runner)
14
+
15
+ pieces = command.split(" ")
16
+ pieces.insert(0, "") if pieces.size == 1
17
+ action, suite = pieces
18
+
19
+ @action = action
20
+ @suite = suite
21
+ @configs = runner.configs
22
+ @suites = runner.suites
23
+ end
24
+
25
+ def run
26
+ @action = "test" if @action == ""
27
+ eval(@action)
28
+ end
29
+
30
+
31
+ def list
32
+ check_configuration
33
+ get_suite_configs
34
+
35
+ say("all subcommands for test suite: #{@suite}", :yellow)
36
+ say("Options:", :yellow)
37
+ SUPPORT_OPTIONS.each do |opt, helper|
38
+ say("\t[#{opt}] \t#{helper}")
39
+ end
40
+ nl
41
+
42
+ @suite_configs["commands"].each do |command, details|
43
+ say("#{@suite} #{command}", :green)
44
+ say("\t#{details["desc"]}\n")
45
+ end
46
+ end
47
+
48
+ def configure
49
+ puts "configure #{@suite}"
50
+ location = @configs.configs["suites"][@suite]["location"]
51
+ location = "" if location == USER_INPUT
52
+ invalid_input = true
53
+ 3.times do
54
+ user_input = ask("suite: #{@suite} source directory:", :default => location).strip
55
+ if user_input =~ /^~/
56
+ user_input = File.expand_path(user_input)
57
+ else
58
+ user_input = File.absolute_path(user_input)
59
+ end
60
+ if Dir.exist?(user_input)
61
+ if File.exist?(File.join(user_input, TEST_SUITE_CONFIG_FILE))
62
+ invalid_input = false
63
+ location = user_input
64
+ break
65
+ else
66
+ say("the configure file: #{yellow(TEST_SUITE_CONFIG_FILE)} " +
67
+ "cannot be found under #{user_input}.")
68
+ end
69
+ else
70
+ say("the directory: #{user_input} is invalid.")
71
+ end
72
+ end
73
+
74
+ if invalid_input
75
+ say("invalid inputs for 3 times. abort!", :red)
76
+ exit(1)
77
+ else
78
+ @configs.configs["suites"][@suite]["location"] = location
79
+ @configs.save
80
+ say("configure suite: #{@suite} successfully.", :green)
81
+ end
82
+ end
83
+
84
+ def test
85
+ check_configuration
86
+ parse_options
87
+ check_if_dirty_state unless @options["--force"]
88
+ get_suite_configs
89
+
90
+ dependencies, command = parse_command
91
+
92
+ threads = []
93
+ pwd = Dir.pwd
94
+ Dir.chdir(@suites.suites["suites"][@suite])
95
+ threads << Thread.new do
96
+ # dependency should be successful before run testing command
97
+ say("preparing test suite: #{@suite}...")
98
+ dependencies.each do |d|
99
+ output = `#{d}`
100
+ unless $? == 0
101
+ say(output)
102
+ say("fail to execute dependency command: #{d}. abort!", :red)
103
+ exit(1)
104
+ end
105
+ end
106
+
107
+ say("\nrun command: #{yellow(command)}")
108
+ system(command)
109
+ end
110
+
111
+ threads.each { |t| t.join }
112
+ Dir.chdir(pwd)
113
+ collector = ClientCollector.new(@runner.command, @suite, @runner)
114
+ collector.post
115
+ end
116
+
117
+ def check_configuration
118
+ unless @suites.suites["suites"].has_key?(@suite)
119
+ say("suite configure file: #{@suites.file} did not has key: #{@suite}")
120
+ exit(1)
121
+ end
122
+ end
123
+
124
+ def check_if_dirty_state
125
+ if dirty_state?
126
+ say("\n%s\n" % [`git status`])
127
+ say("Your current directory has some local modifications, " +
128
+ "please discard or commit them first.\n" +
129
+ "Or use #{yellow("--force")} to bypass git dirty check.")
130
+ exit(1)
131
+ end
132
+ end
133
+
134
+ def dirty_state?
135
+ `which git`
136
+ return false unless $? == 0
137
+
138
+ Dir.chdir(@suites.suites["suites"][@suite])
139
+ (File.directory?(".git") || File.directory?(File.join("..", ".git"))) \
140
+ && `git status --porcelain | wc -l`.to_i > 0
141
+ end
142
+
143
+ def parse_options
144
+ @options = {}
145
+ opts = SUPPORT_OPTIONS.keys
146
+ @args.each do |arg|
147
+ if opts.index(arg)
148
+ @options[arg] = true
149
+ @args.delete(arg)
150
+ end
151
+ end
152
+ end
153
+
154
+ def parse_command
155
+ subcmd = ""
156
+ dependencies = []
157
+ if @args.empty?
158
+ subcmd = @suite_configs["commands"]["default"]["exec"]
159
+ dependencies = @suite_configs["commands"]["default"]["dependencies"]
160
+ elsif @suite_configs["commands"].has_key?(@args[0])
161
+ subcmd = @suite_configs["commands"][@args[0]]["exec"]
162
+ dependencies = @suite_configs["commands"][@args[0]]["dependencies"]
163
+ @args.delete(@args[0])
164
+ else
165
+ say("#{@args[0]} is not invalid sub-command, run as default command")
166
+ subcmd = @suite_configs["commands"]["default"]["exec"]
167
+ dependencies = @suite_configs["commands"]["default"]["dependencies"]
168
+ end
169
+
170
+ unless @args.empty?
171
+ subcmd = subcmd + " " + @args.join(" ")
172
+ end
173
+
174
+ [dependencies, subcmd]
175
+ end
176
+
177
+ def get_suite_configs
178
+ suite_configs_path = File.join(@suites.suites["suites"][@suite],
179
+ CTT::Cli::TEST_SUITE_CONFIG_FILE)
180
+ @suite_configs ||= YAML.load_file(suite_configs_path)
181
+ unless @suite_configs.is_a?(Hash)
182
+ say("invalid yaml format for file: #{suite_configs_path}", :red)
183
+ exit(1)
184
+ end
185
+ @suite_configs
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module CTT::Cli
3
+ class Configs
4
+
5
+ attr_accessor :configs, :suites
6
+
7
+ attr_reader :commands
8
+
9
+ def initialize
10
+ @suites = Suites.new.suites
11
+ load_commands
12
+ end
13
+
14
+ def load_commands
15
+ @commands = STATIC_COMMANDS.dup
16
+ commands = {}
17
+ @suites["suites"].each do |suite, _|
18
+ # for each suite, three commands should be added.
19
+ # - configure suite
20
+ # - suite [subcommand]
21
+ # - list suite
22
+ commands[suite] = {"usage" => "#{suite} [subcommand]",
23
+ "desc" => "run default test for test suite: #{suite}," +
24
+ " if no subcommand is specified"}
25
+
26
+ key = "list #{suite}"
27
+ commands[key] = {"usage" => key,
28
+ "desc" => "list all available subcommands for test suite: #{suite}"}
29
+ end
30
+
31
+ @commands.merge!(commands)
32
+ end
33
+ end
34
+ end
data/lib/cli/consts.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ module CTT::Cli
3
+
4
+ TEST_SUITE_CONFIG_FILE = "orc.yml"
5
+ TEST_RESULT_FILE = "junitResult.xml"
6
+ RESULTS_SERVER_URL = "http://giant.cloudfoundry.com"
7
+
8
+ end
@@ -0,0 +1,36 @@
1
+
2
+ module CTTExtensions
3
+
4
+ def say(message, color = nil, sep = "\n")
5
+ msg = message
6
+ puts Paint[msg, color]
7
+ end
8
+
9
+ def nl
10
+ puts ""
11
+ end
12
+
13
+ def err(message)
14
+ raise CTT::Cli::CliError, message
15
+ end
16
+
17
+ def red(message)
18
+ Paint[message, :red]
19
+ end
20
+
21
+ def yellow(message)
22
+ Paint[message, :yellow]
23
+ end
24
+
25
+ def green(message)
26
+ Paint[message, :green]
27
+ end
28
+
29
+ def cyan(message)
30
+ Paint[message, :cyan]
31
+ end
32
+ end
33
+
34
+ class Object
35
+ include CTTExtensions
36
+ end
data/lib/cli/errors.rb ADDED
@@ -0,0 +1,24 @@
1
+
2
+ module CTT::Cli
3
+ class CliError < StandardError
4
+ attr_reader :exit_code
5
+
6
+ def initialize(*args)
7
+ @exit_code = 1
8
+ super(*args)
9
+ end
10
+
11
+ def self.error_code(code = nil)
12
+ define_method(:error_code) { code }
13
+ end
14
+
15
+ def self.exit_code(code = nil)
16
+ define_method(:exit_code) { code }
17
+ end
18
+
19
+ error_code(42)
20
+ end
21
+
22
+ class UnknownCommand < CliError; error_code(100); end
23
+
24
+ end
data/lib/cli/report.rb ADDED
@@ -0,0 +1,62 @@
1
+
2
+ module CTT::Cli
3
+ class TestReport
4
+
5
+ attr_reader :summary
6
+
7
+ def initialize(xml_file)
8
+ unless File.exists?(xml_file)
9
+ say("Test result file: #{xml_file} does not exist. abort!", :red)
10
+ exit(1)
11
+ end
12
+
13
+ begin
14
+ @doc = LibXML::XML::Document.file(xml_file)
15
+ rescue Exception => e
16
+ say("Test result file: #{xml_file} is not a valid xml document. abort!", :red)
17
+ exit(1)
18
+ end
19
+ end
20
+
21
+ def parse
22
+ @summary = {:total => 0,
23
+ :failed => 0,
24
+ :pending => 0,
25
+ :duration => 0.0,
26
+ :failed_cases => [],
27
+ :pending_cases => []}
28
+
29
+ cases = @doc.find("//case")
30
+ @summary[:total] = cases.size
31
+ get_duration
32
+ cases.each do |c|
33
+ get_failed_case(c)
34
+ get_pending_case(c)
35
+ end
36
+ end
37
+
38
+ def get_duration
39
+ # duration
40
+ duration = @doc.find_first("/result/duration")
41
+ @summary[:duration] += duration.content.to_f
42
+ end
43
+
44
+ def get_failed_case(case_node)
45
+ # failed
46
+ failed = case_node.find_first("errorDetails")
47
+ if failed
48
+ @summary[:failed] += 1
49
+ @summary[:failed_cases] << case_node.find_first("rerunCommand").content
50
+ end
51
+ end
52
+
53
+ def get_pending_case(case_node)
54
+ # pending
55
+ pending = case_node.find_first("skipped")
56
+ if pending.content == "true"
57
+ @summary[:pending] += 1
58
+ @summary[:pending_cases] << case_node.find_first("testName").content
59
+ end
60
+ end
61
+ end
62
+ end
data/lib/cli/runner.rb ADDED
@@ -0,0 +1,134 @@
1
+
2
+ module CTT::Cli
3
+
4
+ class Runner
5
+
6
+ attr_reader :commands, :uuid, :command
7
+ attr_accessor :configs, :suites
8
+
9
+
10
+ # @param [Array] args
11
+ def self.run(args)
12
+ new(args).run
13
+ end
14
+
15
+ def initialize(args)
16
+ @args = args
17
+
18
+ banner = "Usage: orc [<options>] <command> [<args>]"
19
+ @option_parser = OptionParser.new(banner)
20
+
21
+ @configs = Configs.new
22
+ @commands = @configs.commands
23
+ @suites = Suites.new
24
+ @uuid = UUIDTools::UUID.random_create
25
+ end
26
+
27
+ def run
28
+ #puts "the args: #{@args}"
29
+ parse_global_options
30
+
31
+ if @args.empty? && @options.empty?
32
+ say(usage)
33
+ exit(0)
34
+ end
35
+
36
+ if @options[:help]
37
+ Command::Help.new(@args, self).run
38
+ exit(0)
39
+ end
40
+
41
+ find, command, args = search_commands
42
+ unless find
43
+ say("invalid command. abort!", :red)
44
+ exit(1)
45
+ end
46
+
47
+ @command = command
48
+ execute(command, args)
49
+
50
+ end
51
+
52
+ def parse_global_options
53
+ @options = {}
54
+ opts = @option_parser
55
+
56
+ opts.on("-v", "--version", "Show version") do
57
+ @options[:version] = true
58
+ say("orc %s" % [CTT::Cli::VERSION])
59
+ exit(0)
60
+ end
61
+
62
+ opts.on("-h", "--help", "Show help message") do
63
+ @options[:help] = true
64
+ end
65
+
66
+ begin
67
+ @args = @option_parser.order!(@args)
68
+ rescue
69
+ say("invalid command. abort!", :red)
70
+ exit(1)
71
+ end
72
+ end
73
+
74
+ def usage
75
+ @option_parser.to_s
76
+ end
77
+
78
+ def search_commands
79
+ cmds = @commands.keys
80
+
81
+ find = nil
82
+ longest_cmd = ""
83
+ args = []
84
+ size = @args.size
85
+ size.times do |index|
86
+
87
+ longest_cmd = @args[0..(size - index - 1)].join(" ")
88
+ find = cmds.index(longest_cmd)
89
+ if find
90
+ args = @args[(size - index)..-1]
91
+ break
92
+ end
93
+
94
+ end
95
+ [find, longest_cmd, args]
96
+ end
97
+
98
+ def execute(command, args)
99
+ handler = get_command_handler(command, args)
100
+ handler.run
101
+ end
102
+
103
+ def get_command_handler(command, args)
104
+ # handle runtime commands
105
+ #
106
+ @configs.suites["suites"].keys.each do |s|
107
+ if command =~ /#{s}/
108
+ #pieces = command.split(" ")
109
+ #pieces.insert(0, "") if pieces.size == 1
110
+ #action, suite = pieces
111
+ return Command::TestSuite.new(command, args, self)
112
+ end
113
+ end
114
+
115
+ # handle user defined alias
116
+ #
117
+ # TODO
118
+
119
+ # handle static commands
120
+ #
121
+ handler =
122
+ case command
123
+ when "help"
124
+ Command::Help.new(args, self)
125
+ when "add suite", "delete suite", "suites"
126
+ Command::SuitesConfig.new(command, args, self)
127
+ when "tests", "rerun"
128
+ Command::MultipleTests.new(command, args, self)
129
+ else
130
+ nil
131
+ end
132
+ end
133
+ end
134
+ end
data/lib/cli/suites.rb ADDED
@@ -0,0 +1,36 @@
1
+
2
+
3
+ module CTT::Cli
4
+ class Suites
5
+
6
+ SUITES_CONFIG_FILE =
7
+ File.absolute_path(File.join(ENV["HOME"], ".orc/suites.yml"))
8
+
9
+ attr_accessor :suites
10
+
11
+ attr_reader :file
12
+
13
+ def initialize
14
+ load
15
+ save
16
+ end
17
+
18
+ def load
19
+ @file = SUITES_CONFIG_FILE
20
+ unless Dir.exists?(File.dirname(@file))
21
+ Dir.mkdir(File.dirname(@file))
22
+ end
23
+
24
+ if File.exists?(@file)
25
+ @suites = YAML.load_file(@file)
26
+ else
27
+ @suites = {"suites" => {}}
28
+ end
29
+ end
30
+
31
+ def save
32
+ File.open(@file, "w") { |f| f.write YAML.dump(@suites) }
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module CTT
3
+ module Cli
4
+ VERSION = '0.1.0'
5
+ end
6
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,30 @@
1
+
2
+
3
+ require "optparse"
4
+ require "yaml"
5
+ require "paint"
6
+ require "interact"
7
+ require "terminal-table"
8
+ require "libxml"
9
+ require "tempfile"
10
+ require "uuidtools"
11
+ require "etc"
12
+ require "restclient"
13
+
14
+
15
+
16
+ require "cli/version"
17
+ require "cli/consts"
18
+ require "cli/ctt_extensions"
19
+ require "cli/cmd_consts"
20
+ require "cli/suites"
21
+ require "cli/configs"
22
+ require "cli/errors"
23
+ require "cli/runner"
24
+ require "cli/base"
25
+ require "cli/report"
26
+ require "cli/client_collector"
27
+ require "cli/commands/help"
28
+ require "cli/commands/suites_configure"
29
+ require "cli/commands/multiple_tests"
30
+ require "cli/commands/test_suite_management"
@@ -0,0 +1,20 @@
1
+
2
+ require "rspec/core"
3
+ require "fileutils"
4
+
5
+ $:.unshift(File.expand_path("../../lib", __FILE__))
6
+ require "cli"
7
+
8
+ RSpec.configure do |c|
9
+ c.before(:each) do
10
+ config_path = File.expand_path("./config")
11
+ unless Dir.exists?(config_path)
12
+ FileUtils.mkdir(config_path)
13
+ end
14
+ src = File.join(config_path, "template/commands.yml")
15
+ dest = File.join(config_path, "commands.yml")
16
+ FileUtils.cp(src, dest)
17
+ end
18
+
19
+ c.color_enabled = true
20
+ end
@@ -0,0 +1,15 @@
1
+
2
+ require "spec_helper"
3
+
4
+ describe CTT::Cli::Command::Help do
5
+
6
+ it "list help command" do
7
+ args = "help abc def"
8
+ output = `./bin/orc #{args}`
9
+ keywords = ["help", "tests",
10
+ "add suite <Test Suite Path> [alias]"]
11
+ keywords.each do |w|
12
+ output.should include w
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orc-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pin Xie
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json_pure
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.6.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.6.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: progressbar
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.9.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: terminal-table
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.2
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.2
62
+ - !ruby/object:Gem::Dependency
63
+ name: paint
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.5
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.8.5
78
+ - !ruby/object:Gem::Dependency
79
+ name: interact
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.4.8
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.4.8
94
+ - !ruby/object:Gem::Dependency
95
+ name: libxml-ruby
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 2.3.3
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 2.3.3
110
+ - !ruby/object:Gem::Dependency
111
+ name: uuidtools
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 2.1.3
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 2.1.3
126
+ - !ruby/object:Gem::Dependency
127
+ name: rest-client
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 1.6.7
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 1.6.7
142
+ description: Test automation client tool
143
+ email: pxie@vmware.com
144
+ executables:
145
+ - orc
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - bin/orc
150
+ - lib/cli.rb
151
+ - lib/cli/base.rb
152
+ - lib/cli/client_collector.rb
153
+ - lib/cli/cmd_consts.rb
154
+ - lib/cli/commands/help.rb
155
+ - lib/cli/commands/multiple_tests.rb
156
+ - lib/cli/commands/rerun.rb
157
+ - lib/cli/commands/suites_configure.rb
158
+ - lib/cli/commands/test_suite_management.rb
159
+ - lib/cli/configs.rb
160
+ - lib/cli/consts.rb
161
+ - lib/cli/ctt_extensions.rb
162
+ - lib/cli/errors.rb
163
+ - lib/cli/report.rb
164
+ - lib/cli/runner.rb
165
+ - lib/cli/suites.rb
166
+ - lib/cli/version.rb
167
+ - README.md
168
+ - Rakefile
169
+ - config/commands.yml
170
+ - spec/spec_helper.rb
171
+ - spec/unit/help_spec.rb
172
+ homepage: http://www.vmware.com
173
+ licenses: []
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ none: false
180
+ requirements:
181
+ - - ! '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 1.8.19
193
+ signing_key:
194
+ specification_version: 3
195
+ summary: ORC CLI
196
+ test_files:
197
+ - spec/spec_helper.rb
198
+ - spec/unit/help_spec.rb