tac-cli 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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 tac-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/tac 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 tac, 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.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"
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,106 @@
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[:results_file] = @tar_file
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
+ rescue
28
+ end
29
+ break if response.code == 200
30
+ end
31
+ @tar_file.unlink
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 = zip_test_reports(report_path)
91
+ end
92
+
93
+ def zip_test_reports(reports_path)
94
+ tar_file = Tempfile.new(%w(reports .tgz))
95
+ `tar czf #{tar_file.path} #{reports_path} 2>&1`
96
+ unless $?.exitstatus == 0
97
+ say("fail to tarball test reports. abort!", :red)
98
+ tar_file.unlink
99
+ exit(1)
100
+ end
101
+ tar_file
102
+ end
103
+
104
+
105
+ end
106
+ 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 tac"},
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,135 @@
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
+ index = 1
45
+ @suites.suites["suites"].each do |name, _|
46
+ say("#{index}) start to run test suite: #{name}\n", :yellow)
47
+ cmd = TestSuite.new(name, @args, @runner)
48
+ cmd.run
49
+ index += 1
50
+ end
51
+ end
52
+
53
+ def show_summary(rerun = false)
54
+ summary = {:total => 0,
55
+ :failed => 0,
56
+ :pending => 0,
57
+ :duration => 0.0,
58
+ :failed_cases => {},
59
+ :pending_cases => {}
60
+ }
61
+ @suites.suites["suites"].each do |name, path|
62
+ suite_config_path = File.absolute_path(File.join(path, TEST_SUITE_CONFIG_FILE))
63
+ suite_config = YAML.load_file(suite_config_path)
64
+ unless suite_config["results"]
65
+ say("no results field in #{suite_config_path}. abort!", :red)
66
+ exit(1)
67
+ end
68
+
69
+ result_path = File.join(path, suite_config["results"])
70
+ if rerun
71
+ result_file = File.join(get_rerun_folder(result_path), TEST_RESULT_FILE)
72
+ else
73
+ result_file = File.join(result_path, TEST_RESULT_FILE)
74
+ end
75
+
76
+ report = TestReport.new(result_file)
77
+ report.parse
78
+
79
+ summary[:total] += report.summary[:total]
80
+ summary[:failed] += report.summary[:failed]
81
+ summary[:pending] += report.summary[:pending]
82
+ summary[:duration] += report.summary[:duration]
83
+ summary[:failed_cases][name] = report.summary[:failed_cases]
84
+ summary[:pending_cases][name] = report.summary[:pending_cases]
85
+ end
86
+
87
+ print_cases_summary(summary)
88
+ print_failed_cases(summary)
89
+
90
+ end
91
+
92
+ def print_cases_summary(summary)
93
+ say("\nFinished in #{format_time(summary[:duration])}")
94
+
95
+ color = :green
96
+ color = :yellow if summary[:pending] > 0
97
+ color = :red if summary[:failed] > 0
98
+ say("#{summary[:total]} examples, #{summary[:failed]} failures, #{summary[:pending]} pendings", color)
99
+ end
100
+
101
+ def print_failed_cases(summary)
102
+ unless summary[:failed_cases].empty?
103
+ say("\nFailures:")
104
+ summary[:failed_cases].each do |suite, cases|
105
+ say(" Test Suite: #{suite}", :yellow)
106
+ cases.each do |c|
107
+ say(" #{c.strip}", :red)
108
+ end
109
+ end
110
+ say("execute #{yellow("rerun")} command to run all failed cases.")
111
+ end
112
+ end
113
+
114
+ def format_time(t)
115
+ time_str = ''
116
+ time_str += (t / 3600).to_i.to_s + " hours " if t > 3600
117
+ time_str += (t % 3600 / 60).to_i.to_s + " minutes " if t > 60
118
+ time_str += (t % 60).to_f.round(2).to_s + " seconds"
119
+ time_str
120
+ end
121
+
122
+ def get_rerun_folder(result_folder)
123
+ rerun_folder = result_folder
124
+ i = MAX_RERUN_TIMES
125
+ while(i > 0)
126
+ if File.exists?(File.join(result_folder, "rerun#{i}"))
127
+ rerun_folder = File.join(result_folder, "rerun#{i}")
128
+ break
129
+ end
130
+ i -= 1
131
+ end
132
+ rerun_folder
133
+ end
134
+ end
135
+ 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 = "tac.yml"
7
+
8
+ include Interactive
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,183 @@
1
+
2
+ module CTT::Cli::Command
3
+
4
+ class TestSuite < Base
5
+
6
+ #USER_INPUT = "USER_INPUT"
7
+ #TEST_SUITE_CONFIG_FILE = "tac.yml"
8
+ SUPPORT_OPTIONS = {"--force" => "bypass git dirty state check"}
9
+
10
+ include Interactive
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
+ threads << Thread.new do
94
+ Dir.chdir(@suites.suites["suites"][@suite])
95
+ # dependency should be successful before run testing command
96
+ say("preparing test suite: #{@suite}...")
97
+ dependencies.each do |d|
98
+ `#{d}`
99
+ exit(1) unless $? == 0
100
+ end
101
+
102
+ say("\nrun command: #{yellow(command)}")
103
+ system(command)
104
+ end
105
+
106
+ threads.each { |t| t.join }
107
+
108
+ collector = ClientCollector.new(@runner.command, @suite, @runner)
109
+ collector.post
110
+ end
111
+
112
+ def check_configuration
113
+ unless @suites.suites["suites"].has_key?(@suite)
114
+ say("suite configure file: #{@suites.file} did not has key: #{@suite}")
115
+ exit(1)
116
+ end
117
+ end
118
+
119
+ def check_if_dirty_state
120
+ if dirty_state?
121
+ say("\n%s\n" % [`git status`])
122
+ say("Your current directory has some local modifications, " +
123
+ "please discard or commit them first.\n" +
124
+ "Or use #{yellow("--force")} to bypass git dirty check.")
125
+ exit(1)
126
+ end
127
+ end
128
+
129
+ def dirty_state?
130
+ `which git`
131
+ return false unless $? == 0
132
+
133
+ Dir.chdir(@suites.suites["suites"][@suite])
134
+ (File.directory?(".git") || File.directory?(File.join("..", ".git"))) \
135
+ && `git status --porcelain | wc -l`.to_i > 0
136
+ end
137
+
138
+ def parse_options
139
+ @options = {}
140
+ opts = SUPPORT_OPTIONS.keys
141
+ @args.each do |arg|
142
+ if opts.index(arg)
143
+ @options[arg] = true
144
+ @args.delete(arg)
145
+ end
146
+ end
147
+ end
148
+
149
+ def parse_command
150
+ subcmd = ""
151
+ dependencies = []
152
+ if @args.empty?
153
+ subcmd = @suite_configs["commands"]["default"]["exec"]
154
+ dependencies = @suite_configs["commands"]["default"]["dependencies"]
155
+ elsif @suite_configs["commands"].has_key?(@args[0])
156
+ subcmd = @suite_configs["commands"][@args[0]]["exec"]
157
+ dependencies = @suite_configs["commands"][@args[0]]["dependencies"]
158
+ @args.delete(@args[0])
159
+ else
160
+ say("#{@args[0]} is not invalid sub-command, run as default command")
161
+ subcmd = @suite_configs["commands"]["default"]["exec"]
162
+ dependencies = @suite_configs["commands"]["default"]["dependencies"]
163
+ end
164
+
165
+ unless @args.empty?
166
+ subcmd = subcmd + " " + @args.join(" ")
167
+ end
168
+
169
+ [dependencies, subcmd]
170
+ end
171
+
172
+ def get_suite_configs
173
+ suite_configs_path = File.join(@suites.suites["suites"][@suite],
174
+ CTT::Cli::TEST_SUITE_CONFIG_FILE)
175
+ @suite_configs ||= YAML.load_file(suite_configs_path)
176
+ unless @suite_configs.is_a?(Hash)
177
+ say("invalid yaml format for file: #{suite_configs_path}", :red)
178
+ exit(1)
179
+ end
180
+ @suite_configs
181
+ end
182
+ end
183
+ 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 = "tac.yml"
5
+ TEST_RESULT_FILE = "junitResult.xml"
6
+ RESULTS_SERVER_URL = "http://127.0.0.1:4567"
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: tac [<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("tac %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"], ".tac/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.0.3'
5
+ end
6
+ end
@@ -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/tac #{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: tac-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pin Xie
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-03 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
+ - tac
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - bin/tac
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: TAC CLI
196
+ test_files:
197
+ - spec/spec_helper.rb
198
+ - spec/unit/help_spec.rb