teuton 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/teuton +2 -3
- data/docs/commands/readme.md +1 -1
- data/docs/devel/todo.md +87 -44
- data/docs/dsl/send.md +3 -3
- data/docs/dsl/show.md +2 -2
- data/docs/install/t-node.md +4 -14
- data/docs/learn/18-log.md +55 -20
- data/docs/tutorial/es/nginx/README.md +4 -4
- data/lib/teuton/case/action.rb +31 -0
- data/lib/teuton/case/case.rb +8 -12
- data/lib/teuton/case/close.rb +1 -1
- data/lib/teuton/case/dsl/expect.rb +10 -11
- data/lib/teuton/case/dsl/expect_sequence.rb +1 -2
- data/lib/teuton/case/dsl/macro.rb +3 -2
- data/lib/teuton/case/dsl/run.rb +4 -5
- data/lib/teuton/case/dsl/run_script.rb +3 -3
- data/lib/teuton/case/dsl/send.rb +2 -18
- data/lib/teuton/case/dsl/target.rb +6 -4
- data/lib/teuton/case/dsl/upload.rb +2 -5
- data/lib/teuton/case/dsl/weight.rb +3 -3
- data/lib/teuton/case/execute/execute_base.rb +4 -4
- data/lib/teuton/case/execute/execute_local.rb +2 -2
- data/lib/teuton/case/execute/execute_manager.rb +6 -6
- data/lib/teuton/case/execute/execute_ssh.rb +6 -6
- data/lib/teuton/case/execute/execute_telnet.rb +4 -4
- data/lib/teuton/case/play.rb +7 -8
- data/lib/teuton/case_manager/case_manager.rb +4 -4
- data/lib/teuton/case_manager/dsl.rb +7 -10
- data/lib/teuton/case_manager/export_manager.rb +6 -12
- data/lib/teuton/case_manager/ext/check_cases.rb +1 -1
- data/lib/teuton/cli/app.rb +123 -0
- data/lib/teuton/cli/run.rb +38 -0
- data/lib/teuton/errors.rb +94 -0
- data/lib/teuton/readme/action.rb +29 -0
- data/lib/teuton/readme/dsl/all.rb +7 -5
- data/lib/teuton/readme/dsl/expect.rb +3 -3
- data/lib/teuton/readme/dsl/run.rb +5 -11
- data/lib/teuton/readme/main.rb +13 -6
- data/lib/teuton/readme/readme.rb +12 -15
- data/lib/teuton/report/formatter/base_formatter.rb +2 -2
- data/lib/teuton/report/formatter/default/txt.rb +2 -2
- data/lib/teuton/report/formatter/formatter.rb +4 -13
- data/lib/teuton/report/formatter/resume/txt.rb +2 -2
- data/lib/teuton/utils/config_file_reader.rb +4 -12
- data/lib/teuton/utils/logger.rb +15 -10
- data/lib/teuton/utils/name_file_finder.rb +3 -11
- data/lib/teuton/version.rb +1 -1
- data/lib/teuton.rb +6 -11
- metadata +6 -2
- data/lib/teuton/cli.rb +0 -120
data/lib/teuton/case/dsl/send.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module DSL
|
|
4
|
-
# * send,
|
|
4
|
+
# * send, tempdir, remote_tempdir
|
|
5
5
|
def send(logfile, args = {})
|
|
6
6
|
return if skip?
|
|
7
7
|
|
|
@@ -38,7 +38,7 @@ module DSL
|
|
|
38
38
|
logfile.write "#{msg}\n"
|
|
39
39
|
logfile.flush
|
|
40
40
|
rescue => e
|
|
41
|
-
msg = Rainbow("==> [FAIL] Case #{get(:tt_members)}: 'scp #{localfilepath}' to #{remotefilepath}").
|
|
41
|
+
msg = Rainbow("==> [FAIL] Case #{get(:tt_members)}: 'scp #{localfilepath}' to #{remotefilepath}").yellow
|
|
42
42
|
msg += "\n--> [ERROR] #{e}"
|
|
43
43
|
verboseln(msg)
|
|
44
44
|
logfile.write "#{msg}\n"
|
|
@@ -46,26 +46,10 @@ module DSL
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
def tempfile(input = nil)
|
|
50
|
-
return @action[:tempfile] if input.nil?
|
|
51
|
-
|
|
52
|
-
name = input
|
|
53
|
-
name = "teuton.tmp" if input == :default
|
|
54
|
-
|
|
55
|
-
@action[:tempfile] = File.join(@tempdir, name)
|
|
56
|
-
@action[:remote_tempfile] = File.join(remote_tempdir, name)
|
|
57
|
-
|
|
58
|
-
@action[:tempfile]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
49
|
def tempdir
|
|
62
50
|
@tempdir
|
|
63
51
|
end
|
|
64
52
|
|
|
65
|
-
def remote_tempfile
|
|
66
|
-
@action[:remote_tempfile]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
53
|
def remote_tempdir
|
|
70
54
|
File.join("/", "tmp") # TODO: Remove this?
|
|
71
55
|
end
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
# DSL#target
|
|
4
4
|
module DSL
|
|
5
5
|
def readme(_text)
|
|
6
|
-
# Usefull
|
|
6
|
+
# Usefull when running.
|
|
7
|
+
# It's works only with "teuton reamde" command action.
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def target(desc, args = {})
|
|
10
|
-
@action
|
|
11
|
-
@action
|
|
11
|
+
@action.description = desc.to_s
|
|
12
|
+
@action.asset = args[:asset].to_s if args[:asset]
|
|
12
13
|
w = args[:weight] || 1.0
|
|
13
14
|
weight(w)
|
|
14
15
|
end
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
alias_method :goal, :target # DEPRECATE
|
|
16
18
|
end
|
|
@@ -15,10 +15,7 @@ module DSL
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def upload_one(localpath, args = {})
|
|
18
|
-
if args[:to].nil?
|
|
19
|
-
Logger.err("ERROR upload requires to: XXX")
|
|
20
|
-
exit 1
|
|
21
|
-
end
|
|
18
|
+
raise ArgumentError, "upload requires 'to: XXX'" if args[:to].nil?
|
|
22
19
|
|
|
23
20
|
host = get_host(args[:to])
|
|
24
21
|
if host.protocol == "ssh"
|
|
@@ -35,7 +32,7 @@ module DSL
|
|
|
35
32
|
verbose(Rainbow("!").green)
|
|
36
33
|
end
|
|
37
34
|
elsif host.protocol != "local"
|
|
38
|
-
msg = Rainbow("==> [ERROR] upload: Incorrect protocol(#{host.protocol})").red
|
|
35
|
+
msg = Rainbow("==> [ERROR] upload: Incorrect protocol(#{host.protocol})").bright.red
|
|
39
36
|
verboseln(msg)
|
|
40
37
|
end
|
|
41
38
|
end
|
|
@@ -2,11 +2,11 @@ module DSL
|
|
|
2
2
|
def weight(value = nil)
|
|
3
3
|
# Set weight value for the action
|
|
4
4
|
if value.nil?
|
|
5
|
-
@action
|
|
5
|
+
@action.weight
|
|
6
6
|
elsif value == :default
|
|
7
|
-
@action
|
|
7
|
+
@action.weight = 1.0
|
|
8
8
|
else
|
|
9
|
-
@action
|
|
9
|
+
@action.weight = value.to_f
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class ExecuteBase
|
|
2
2
|
def initialize(parent)
|
|
3
3
|
@parent = parent
|
|
4
|
-
# READ: @config, cmd = action
|
|
4
|
+
# READ: @config, cmd = action.command
|
|
5
5
|
# WRITE: @action, @result, @session
|
|
6
6
|
end
|
|
7
7
|
|
|
@@ -46,9 +46,9 @@ class ExecuteBase
|
|
|
46
46
|
begin
|
|
47
47
|
text = ec.convert(text)
|
|
48
48
|
rescue => e
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
warn Rainbow("[ERROR] ExecuteBase: #{e}").bright.red
|
|
50
|
+
warn Rainbow("[ERROR] Declare text encoding. Example:").white
|
|
51
|
+
warn Rainbw("[ERROR] run 'command', on: :host, encoding: 'ISO-8859-1'").white
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
text.split("\n").compact
|
|
@@ -8,8 +8,8 @@ class ExecuteLocal < ExecuteBase
|
|
|
8
8
|
include Verbose
|
|
9
9
|
|
|
10
10
|
def call
|
|
11
|
-
action
|
|
12
|
-
response = my_execute(action
|
|
11
|
+
action.conn_type = :local
|
|
12
|
+
response = my_execute(action.command, action.encoding)
|
|
13
13
|
result.exitcode = response[:exitcode]
|
|
14
14
|
result.content = response[:content]
|
|
15
15
|
end
|
|
@@ -5,7 +5,7 @@ require_relative "execute_telnet"
|
|
|
5
5
|
class ExecuteManager
|
|
6
6
|
def initialize(parent)
|
|
7
7
|
@parent = parent
|
|
8
|
-
# READ: @config, cmd = action
|
|
8
|
+
# READ: @config, cmd = action.command
|
|
9
9
|
# WRITE: @action, @result, @session
|
|
10
10
|
# my_execute, encode_and_split methods?
|
|
11
11
|
end
|
|
@@ -13,7 +13,7 @@ class ExecuteManager
|
|
|
13
13
|
def call(host)
|
|
14
14
|
start_time = Time.now
|
|
15
15
|
run_on(host)
|
|
16
|
-
action
|
|
16
|
+
action.duration = (Time.now - start_time).round(3)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
private
|
|
@@ -34,16 +34,16 @@ class ExecuteManager
|
|
|
34
34
|
protocol = config.get(:"#{host}_protocol")
|
|
35
35
|
ip = config.get(:"#{host}_ip")
|
|
36
36
|
|
|
37
|
-
if protocol.to_s.
|
|
37
|
+
if protocol.to_s.casecmp("local").zero? || host.to_s == "localhost"
|
|
38
38
|
# Protocol force => local
|
|
39
39
|
ExecuteLocal.new(@parent).call
|
|
40
|
-
elsif protocol.to_s.
|
|
40
|
+
elsif protocol.to_s.casecmp("ssh").zero?
|
|
41
41
|
# Protocol force => ssh
|
|
42
42
|
ExecuteSSH.new(@parent).call(host)
|
|
43
|
-
elsif protocol.to_s.
|
|
43
|
+
elsif protocol.to_s.casecmp("telnet").zero?
|
|
44
44
|
# Protocol force => telnet
|
|
45
45
|
ExecuteTelnet.new(@parent).call(host)
|
|
46
|
-
elsif ip.to_s.
|
|
46
|
+
elsif ip.to_s.casecmp("localhost").zero? || ip.to_s.include?("127.0.0.")
|
|
47
47
|
# run_cmd_localhost
|
|
48
48
|
ExecuteLocal.new(@parent).call
|
|
49
49
|
elsif ip == "NODATA"
|
|
@@ -6,7 +6,7 @@ require_relative "execute_base"
|
|
|
6
6
|
|
|
7
7
|
class ExecuteSSH < ExecuteBase
|
|
8
8
|
def call(input_hostname)
|
|
9
|
-
action
|
|
9
|
+
action.conn_type = :ssh
|
|
10
10
|
hostname = input_hostname.to_s
|
|
11
11
|
ip = config.get(:"#{hostname}_ip").to_s
|
|
12
12
|
username = config.get(:"#{hostname}_username").to_s
|
|
@@ -20,14 +20,14 @@ class ExecuteSSH < ExecuteBase
|
|
|
20
20
|
ip2 = ip
|
|
21
21
|
username2 = username
|
|
22
22
|
password2 = password
|
|
23
|
-
command2 = action
|
|
23
|
+
command2 = action.command
|
|
24
24
|
hostname = config.get(:"#{hostname}_route")
|
|
25
25
|
ip = config.get(:"#{hostname}_ip").to_s
|
|
26
26
|
username = config.get(:"#{hostname}_username").to_s
|
|
27
27
|
password = config.get(:"#{hostname}_password").to_s
|
|
28
28
|
ostype = config.get(:"#{hostname}_ostype").to_s
|
|
29
29
|
|
|
30
|
-
action
|
|
30
|
+
action.command = if ostype.downcase.start_with? "win"
|
|
31
31
|
"echo y | plink #{username2}@#{ip2} -ssh -pw #{password2} \"#{command2}\""
|
|
32
32
|
else
|
|
33
33
|
"sshpass -p #{password2} #{username2}@#{ip2} #{command2}"
|
|
@@ -49,7 +49,7 @@ class ExecuteSSH < ExecuteBase
|
|
|
49
49
|
)
|
|
50
50
|
end
|
|
51
51
|
if sessions[hostname].instance_of? Net::SSH::Connection::Session
|
|
52
|
-
text = sessions[hostname].exec!(action
|
|
52
|
+
text = sessions[hostname].exec!(action.command)
|
|
53
53
|
exitcode = text.exitstatus
|
|
54
54
|
else
|
|
55
55
|
text = "TEUTON_ERROR_SSH_NO_CONNECTION"
|
|
@@ -84,9 +84,9 @@ class ExecuteSSH < ExecuteBase
|
|
|
84
84
|
text = "TEUTON_ERROR_SSH"
|
|
85
85
|
exitcode = -1
|
|
86
86
|
log("[#{e.class}] SSH on <#{username}@#{ip}>" \
|
|
87
|
-
" exec: #{action
|
|
87
|
+
" exec: #{action.command}", :error)
|
|
88
88
|
end
|
|
89
89
|
result.exitcode = exitcode
|
|
90
|
-
result.content = encode_and_split(action
|
|
90
|
+
result.content = encode_and_split(action.encoding, text)
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -4,7 +4,7 @@ require_relative "execute_base"
|
|
|
4
4
|
|
|
5
5
|
class ExecuteTelnet < ExecuteBase
|
|
6
6
|
def call(input_hostname)
|
|
7
|
-
action
|
|
7
|
+
action.conn_type = :telnet
|
|
8
8
|
hostname = input_hostname.to_s
|
|
9
9
|
ip = config.get((hostname + "_ip").to_sym)
|
|
10
10
|
port = config.get((hostname + "_port").to_sym)
|
|
@@ -31,7 +31,7 @@ class ExecuteTelnet < ExecuteBase
|
|
|
31
31
|
password = config.get((hostname + "_password").to_sym).to_s
|
|
32
32
|
h.login(username, password)
|
|
33
33
|
end
|
|
34
|
-
h.cmd(action
|
|
34
|
+
h.cmd(action.command) { |i| text << i }
|
|
35
35
|
h.close
|
|
36
36
|
sessions[hostname] = :ok
|
|
37
37
|
exitcode = 0
|
|
@@ -51,11 +51,11 @@ class ExecuteTelnet < ExecuteBase
|
|
|
51
51
|
sessions[hostname] = :nosession
|
|
52
52
|
conn_status[hostname] = :error
|
|
53
53
|
log(" ExceptionType=<#{e.class}> doing telnet on <#{username}@#{ip}>" \
|
|
54
|
-
" exec: #{action
|
|
54
|
+
" exec: #{action.command}", :error)
|
|
55
55
|
log(" └── username=<#{username}>, password=<#{password}>," \
|
|
56
56
|
" ip=<#{ip}>, HOSTID=<#{hostname}>", :warn)
|
|
57
57
|
end
|
|
58
58
|
result.exitcode = exitcode
|
|
59
|
-
result.content = encode_and_split(action
|
|
59
|
+
result.content = encode_and_split(action.encoding, text)
|
|
60
60
|
end
|
|
61
61
|
end
|
data/lib/teuton/case/play.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
class Case
|
|
4
4
|
# READ: sessions, config, groups, action, report
|
|
5
|
-
# TODO: groups from Project or from Case???
|
|
6
5
|
def play
|
|
7
6
|
if skip?
|
|
8
7
|
verbose Rainbow("S").green
|
|
@@ -25,18 +24,18 @@ class Case
|
|
|
25
24
|
|
|
26
25
|
def play_groups_in_sequence
|
|
27
26
|
verboseln "\n==> Running case [#{@config.get(:tt_members)}]" if get(:tt_sequence) == true
|
|
28
|
-
@groups.each do |
|
|
29
|
-
@action
|
|
30
|
-
instance_eval(&
|
|
27
|
+
@groups.each do |group|
|
|
28
|
+
@action.groupname = group[:name]
|
|
29
|
+
instance_eval(&group[:block])
|
|
31
30
|
end
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
def play_groups_in_parallel
|
|
35
34
|
verboseln "Running case [#{@config.get(:tt_members)}]"
|
|
36
|
-
@groups.each do |
|
|
37
|
-
verbose "* Processing [#{
|
|
38
|
-
@action
|
|
39
|
-
instance_eval(&
|
|
35
|
+
@groups.each do |group|
|
|
36
|
+
verbose "* Processing [#{group[:name]}] "
|
|
37
|
+
@action.groupname = group[:name]
|
|
38
|
+
instance_eval(&group[:block])
|
|
40
39
|
verbose "\n"
|
|
41
40
|
end
|
|
42
41
|
verboseln "\n"
|
|
@@ -29,11 +29,11 @@ class CaseManager
|
|
|
29
29
|
check_cases!
|
|
30
30
|
instance_eval(&block)
|
|
31
31
|
# Run export if user pass option command "--export=FORMAT"
|
|
32
|
-
|
|
33
|
-
export(format:
|
|
32
|
+
option = Project.value[:options]["export"]
|
|
33
|
+
export(format: option.to_sym) unless option.nil?
|
|
34
34
|
# Accept "configfile" param REVISE There exists?
|
|
35
|
-
|
|
36
|
-
export(format:
|
|
35
|
+
option = Project.value[:options]["configfile"]
|
|
36
|
+
export(format: option.to_sym) unless option.nil?
|
|
37
37
|
# TODO: Export Stats
|
|
38
38
|
# StatsManager.new.call(@cases)
|
|
39
39
|
end
|
|
@@ -13,19 +13,19 @@ end
|
|
|
13
13
|
alias def_macro define_macro
|
|
14
14
|
alias defmacro define_macro
|
|
15
15
|
|
|
16
|
-
# Define a group of [target/run/expect]s
|
|
17
|
-
# @param name (String) Group name
|
|
18
|
-
# @param block (Block) Tests code
|
|
19
16
|
def group(name, &block)
|
|
20
17
|
Project.value[:groups] << { name: name, block: block }
|
|
21
18
|
end
|
|
22
19
|
alias task group
|
|
23
20
|
|
|
24
21
|
def play(&block)
|
|
25
|
-
# Start test
|
|
26
22
|
CaseManager.new.play(&block)
|
|
27
23
|
end
|
|
28
|
-
|
|
24
|
+
|
|
25
|
+
def start(...)
|
|
26
|
+
Logger.warn "[DEPRECATED] Replace 'start do' by `play do'"
|
|
27
|
+
play(...)
|
|
28
|
+
end
|
|
29
29
|
|
|
30
30
|
def use(filename)
|
|
31
31
|
filename += ".rb"
|
|
@@ -36,10 +36,7 @@ def use(filename)
|
|
|
36
36
|
begin
|
|
37
37
|
require_relative findfiles.first
|
|
38
38
|
Project.value[:uses] << File.basename(findfiles.first)
|
|
39
|
-
rescue => e
|
|
40
|
-
|
|
41
|
-
puts "[ERROR] File not found!"
|
|
42
|
-
puts "[ERROR] Fix line <use '#{filename}'>"
|
|
43
|
-
exit 1
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
raise Teuton::CaseManagerDslError.new(e, filename)
|
|
44
41
|
end
|
|
45
42
|
end
|
|
@@ -14,23 +14,17 @@ class ExportManager
|
|
|
14
14
|
# @param cases (Array)
|
|
15
15
|
# @param input (Hash) Selected export options
|
|
16
16
|
def call(main_report, cases, args)
|
|
17
|
-
if args.class != Hash
|
|
18
|
-
puts Rainbow("[ERROR] ExportManager: argument error!").red
|
|
19
|
-
puts Rainbow("[ERROR] Fix line <export #{args}>").red
|
|
20
|
-
puts Rainbow("[ERROR] Replace by <export format: 'txt'>").red
|
|
21
|
-
puts ""
|
|
22
|
-
exit 1
|
|
23
|
-
end
|
|
17
|
+
raise Teuton::ExportArgumentError, args if args.class != Hash
|
|
24
18
|
|
|
25
19
|
# Step 1: Validate options
|
|
26
20
|
options = strings2symbols(args)
|
|
27
21
|
options[:format] = Project.value[:format] if options[:format].nil?
|
|
28
22
|
|
|
29
23
|
unless Formatter.available_formats.include? options[:format]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
Logger.warn "ExportManager: Unkown format!"
|
|
25
|
+
Logger.warn "Fix line <export format: #{options[:format]}>"
|
|
26
|
+
Logger.warn "Available formats: #{Formatter.available_formats.join(", ")}."
|
|
27
|
+
Logger.warn "Using default format <txt>."
|
|
34
28
|
options[:format] = :txt
|
|
35
29
|
end
|
|
36
30
|
|
|
@@ -84,7 +78,7 @@ class ExportManager
|
|
|
84
78
|
"%<hour>02d%<min>02d%<sec>02d", data)
|
|
85
79
|
preserve_dir = File.join(srcdir, subdir)
|
|
86
80
|
|
|
87
|
-
|
|
81
|
+
Logger.info "Preserving files => #{preserve_dir}"
|
|
88
82
|
FileUtils.mkdir(preserve_dir)
|
|
89
83
|
Dir.glob(File.join(srcdir, "**.*")).each { |file| FileUtils.cp(file, preserve_dir) }
|
|
90
84
|
end
|
|
@@ -69,7 +69,7 @@ module CheckCasesExtension
|
|
|
69
69
|
# 3) And order to build hall of fame
|
|
70
70
|
def close_reports_for_all_cases(uniques)
|
|
71
71
|
threads = []
|
|
72
|
-
@cases.each { |c| threads << Thread.new { c.close
|
|
72
|
+
@cases.each { |c| threads << Thread.new { c.close(uniques) } }
|
|
73
73
|
threads.each(&:join)
|
|
74
74
|
|
|
75
75
|
HallOfFame.new(@cases).call
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
require_relative "../version"
|
|
3
|
+
require_relative "../../teuton"
|
|
4
|
+
|
|
5
|
+
module Teuton
|
|
6
|
+
module CLI
|
|
7
|
+
class App < Thor
|
|
8
|
+
map ["h", "-h", "--help"] => "help"
|
|
9
|
+
|
|
10
|
+
map ["n", "-n", "--new"] => "new"
|
|
11
|
+
desc "new DIRECTORY", "Create skeleton for a new project"
|
|
12
|
+
long_desc <<-LONGDESC
|
|
13
|
+
Create files for a new project.
|
|
14
|
+
LONGDESC
|
|
15
|
+
def new(projectpath)
|
|
16
|
+
Teuton.create(projectpath)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
map ["c", "-c", "--check"] => "check"
|
|
20
|
+
option :onlyconfig, type: :boolean
|
|
21
|
+
option :color, type: :boolean
|
|
22
|
+
option :cname, type: :string
|
|
23
|
+
option :cpath, type: :string
|
|
24
|
+
desc "check [OPTIONS] DIRECTORY", "Check test and config file content"
|
|
25
|
+
long_desc <<~LONGDESC
|
|
26
|
+
|
|
27
|
+
(1) teuton check PATH/TO/DIR , Check content of start.rb and config.yaml files.
|
|
28
|
+
|
|
29
|
+
(2) teuton check PATH/TO/DIR --cname=demo , Check content of start.rb and demo.yaml files:
|
|
30
|
+
|
|
31
|
+
(3) teuton check PATH/TO/DIR --onlyconfig , Only show config file recomendations
|
|
32
|
+
|
|
33
|
+
(4) teuton check PATH/TO/DIR/foo.rb , Check content of foo.rb and foo.yaml files.
|
|
34
|
+
|
|
35
|
+
(5) teuton check PATH/TO/DIR/foo.rb --cname=demo , Check content of foo.rb and demo.yaml files.
|
|
36
|
+
|
|
37
|
+
LONGDESC
|
|
38
|
+
def check(projectpath)
|
|
39
|
+
Teuton.check(projectpath, options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
map ["co", "-co", "--config"] => "config"
|
|
43
|
+
desc "config [OPTIONS] DIRECTORY", "Suggest configuration."
|
|
44
|
+
long_desc <<~LONGDESC
|
|
45
|
+
config DIRECTORY, "Suggest the content of the configuration file based on the test"
|
|
46
|
+
LONGDESC
|
|
47
|
+
def config(projectpath)
|
|
48
|
+
# Check Test: suggest the content of the configuration file based on the test
|
|
49
|
+
opt = {"onlyconfig" => true}
|
|
50
|
+
Teuton.check(projectpath, opt)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
map ["--readme"] => "readme"
|
|
54
|
+
option :lang, type: :string
|
|
55
|
+
desc "readme DIRECTORY", "Show README extracted from test contents"
|
|
56
|
+
long_desc <<-LONGDESC
|
|
57
|
+
|
|
58
|
+
(1) teuton readme foo
|
|
59
|
+
, Create README.md from foo/start.rb.
|
|
60
|
+
|
|
61
|
+
(2) teuton readme foo/demo.rb
|
|
62
|
+
, Create README.md from foo/demo.rb.
|
|
63
|
+
LONGDESC
|
|
64
|
+
|
|
65
|
+
def readme(projectpath)
|
|
66
|
+
Teuton.readme(projectpath, options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
map ["--run", "run"] => "play"
|
|
70
|
+
option :export, type: :string
|
|
71
|
+
option :cname, type: :string
|
|
72
|
+
option :cpath, type: :string
|
|
73
|
+
option :case, type: :string
|
|
74
|
+
option :color, type: :boolean
|
|
75
|
+
option :quiet, type: :boolean
|
|
76
|
+
desc "[run] [OPTIONS] DIRECTORY", "Run test from directory"
|
|
77
|
+
long_desc <<-LONGDESC
|
|
78
|
+
This function execute challenge from specified directory.
|
|
79
|
+
By default, show progress on the screen.
|
|
80
|
+
|
|
81
|
+
Let's see others options:
|
|
82
|
+
|
|
83
|
+
(1) teuton foo, run challenge from foo/start.rb with foo/config.yaml config file.
|
|
84
|
+
|
|
85
|
+
(2) teuton run foo, same as (1).
|
|
86
|
+
|
|
87
|
+
(3) teuton run --export=json foo, run challenge and export using json format.
|
|
88
|
+
Others output formats availables are: txt, html, yaml, json and colored_text.
|
|
89
|
+
|
|
90
|
+
(4) teuton run --cname=demo foo, run challenge from foo/start.rb with foo/demo.yaml config file.
|
|
91
|
+
|
|
92
|
+
(5) teuton foo/demo.rb, Run challenge from foo/demo.rb with foo/demo.yaml config file.
|
|
93
|
+
|
|
94
|
+
LONGDESC
|
|
95
|
+
def play(filepath)
|
|
96
|
+
Teuton.run(filepath, options)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
map ["v", "-v", "--version"] => "version"
|
|
100
|
+
desc "version", "Show the program version"
|
|
101
|
+
def version
|
|
102
|
+
puts "#{Teuton::APPNAME} version #{Teuton::VERSION}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# These inputs are equivalents:
|
|
107
|
+
# * teuton dir/foo
|
|
108
|
+
# * teuton run dir/foo
|
|
109
|
+
def method_missing(method, *_args, &_block)
|
|
110
|
+
play(method.to_s)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
114
|
+
# Respond to missing methods name
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.exit_on_failure?
|
|
119
|
+
true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "app"
|
|
4
|
+
|
|
5
|
+
module Teuton::CLI
|
|
6
|
+
def self.run(argv)
|
|
7
|
+
begin
|
|
8
|
+
Teuton::CLI::App.start(argv)
|
|
9
|
+
exit 0
|
|
10
|
+
rescue Interrupt
|
|
11
|
+
puts "\nExecution cancelled by the user."
|
|
12
|
+
exit 130
|
|
13
|
+
rescue Teuton::CaseManagerDslError => e
|
|
14
|
+
lines = e.message.split("\n")
|
|
15
|
+
head = lines.shift
|
|
16
|
+
warn "Error | #{head}"
|
|
17
|
+
lines.each { |line| warn Rainbow("Error| #{line}").white }
|
|
18
|
+
rescue Teuton::ConfigFileError => e
|
|
19
|
+
lines = e.message.split("\n")
|
|
20
|
+
head = lines.shift
|
|
21
|
+
warn "Error | #{head}"
|
|
22
|
+
lines.each { |line| warn Rainbow("Error| #{line}").white }
|
|
23
|
+
rescue Teuton::MainScriptNotFoundError => e
|
|
24
|
+
warn "Error | #{e.message}"
|
|
25
|
+
rescue Teuton::Error => e
|
|
26
|
+
warn "Controlled error: #{e.message}"
|
|
27
|
+
e.backtrace&.first(3)&.each do |line|
|
|
28
|
+
warn Rainbow("Error | #{line}").white
|
|
29
|
+
end
|
|
30
|
+
rescue => e
|
|
31
|
+
warn "Unexpected error: #{e.message}"
|
|
32
|
+
raise e if ENV["DEBUG"]
|
|
33
|
+
|
|
34
|
+
e.backtrace&.first(3)&.each { |line| warn Rainbow("Error | #{line}").white }
|
|
35
|
+
end
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Teuton
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
class CaseManagerDslError < Error
|
|
5
|
+
attr_reader :original_error, :filename
|
|
6
|
+
|
|
7
|
+
def initialize(original_error, filename)
|
|
8
|
+
@original_error = original_error
|
|
9
|
+
@filename = filename
|
|
10
|
+
super(build_message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def build_message
|
|
16
|
+
<<~MSG.strip
|
|
17
|
+
case_manager/dsl: #{@original_error}
|
|
18
|
+
File not found! Fix line <use '#{@filename}'>
|
|
19
|
+
MSG
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ConfigurationError < Error; end
|
|
24
|
+
|
|
25
|
+
class ConfigFileError < Error
|
|
26
|
+
attr_reader :filepath, :original_error
|
|
27
|
+
|
|
28
|
+
def initialize(filepath, original_error)
|
|
29
|
+
@filepath = filepath
|
|
30
|
+
@original_error = original_error
|
|
31
|
+
super(build_message)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_message
|
|
37
|
+
<<~MSG.strip
|
|
38
|
+
Reading configurations file:
|
|
39
|
+
- File : #{@filepath}
|
|
40
|
+
- Type : #{@original_error}
|
|
41
|
+
MSG
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class InputError < Error; end
|
|
46
|
+
class ExecutionError < Error; end
|
|
47
|
+
|
|
48
|
+
class ExportArgumentError < Error
|
|
49
|
+
attr_reader :args
|
|
50
|
+
|
|
51
|
+
def initialize(args)
|
|
52
|
+
@args = args
|
|
53
|
+
super(build_message)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def build_message
|
|
59
|
+
<<~MSG.strip
|
|
60
|
+
ExportManager: argument error!
|
|
61
|
+
- Fix line <export #{@args}>
|
|
62
|
+
- Replace by <export format: 'txt'>
|
|
63
|
+
MSG
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class MainScriptNotFoundError < Error
|
|
68
|
+
attr_reader :script_path
|
|
69
|
+
|
|
70
|
+
def initialize(script_path)
|
|
71
|
+
@script_path = script_path
|
|
72
|
+
super(build_message)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_message
|
|
78
|
+
<<~MSG.strip
|
|
79
|
+
Main script file not found!
|
|
80
|
+
- File: #{script_path}
|
|
81
|
+
MSG
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class UnexpectedSequenceTypeError < Error
|
|
86
|
+
attr_reader :received_class
|
|
87
|
+
|
|
88
|
+
def initialize(value_class)
|
|
89
|
+
super("expect_sequence #{value_class}")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class UploadArgumentError < ArgumentError; end
|
|
94
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Readme
|
|
2
|
+
class Action
|
|
3
|
+
attr_reader :id
|
|
4
|
+
attr_accessor :description, :weight, :readme, :host, :exec
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@id = 0
|
|
8
|
+
@description = nil
|
|
9
|
+
@host = nil
|
|
10
|
+
@readme = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def reset
|
|
14
|
+
@id += 1
|
|
15
|
+
@description = nil
|
|
16
|
+
@host = nil
|
|
17
|
+
@readme = []
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
{
|
|
23
|
+
id: id,
|
|
24
|
+
description: description, weight: weight, readme: readme,
|
|
25
|
+
host: host, exec: exec
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|