spectre-core 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ require 'ostruct'
2
+
3
+ module Spectre
4
+ module Mixin
5
+ class << self
6
+ @@mixins = {}
7
+
8
+ def mixin desc, &block
9
+ @@mixins[desc] = block
10
+ end
11
+
12
+ def run desc, with: []
13
+ raise "no mixin with desc '#{desc}' defined" unless @@mixins.has_key? desc
14
+ Logger.log_debug "running mixin '#{desc}'"
15
+
16
+ if with.is_a? Array
17
+ @@mixins[desc].call *with
18
+ else
19
+ @@mixins[desc].call with
20
+ end
21
+ end
22
+
23
+ alias_method :also, :run
24
+ alias_method :step, :run
25
+ end
26
+
27
+ Spectre.register do |config|
28
+ if not config.has_key? 'mixin_patterns'
29
+ return
30
+ end
31
+
32
+ config['mixin_patterns'].each do |pattern|
33
+ Dir.glob(pattern).each do|f|
34
+ require_relative File.join(Dir.pwd, f)
35
+ end
36
+ end
37
+ end
38
+
39
+ Spectre.delegate :mixin, :run, :also, :step, to: Mixin
40
+ end
41
+ end
@@ -0,0 +1,97 @@
1
+ require 'mysql2'
2
+
3
+ module Spectre
4
+ module MySQL
5
+
6
+ class MySqlQuery < DslClass
7
+ def initialize query
8
+ @__query = query
9
+ end
10
+
11
+ def host hostname
12
+ @__query['host'] = hostname
13
+ end
14
+
15
+ def username user
16
+ @__query['username'] = user
17
+ end
18
+
19
+ def password pass
20
+ @__query['password'] = pass
21
+ end
22
+
23
+ def database name
24
+ @__query['database'] = name
25
+ end
26
+
27
+ def query statement
28
+ @__query['query'] = [] if not @__query.has_key? 'query'
29
+ @__query['query'].append(statement)
30
+ end
31
+ end
32
+
33
+ class << self
34
+ @@mysql_cfg = {}
35
+ @@result = nil
36
+ @@last_conn = nil
37
+
38
+ def mysql name = nil, &block
39
+ query = {}
40
+
41
+ if name != nil and @@mysql_cfg.has_key? name
42
+ query.merge! @@mysql_cfg[name]
43
+ raise "No `host' set for MySQL client '#{name}'. Check your MySQL config in your environment." if !query['host']
44
+ elsif name != nil
45
+ query['host'] = name
46
+ elsif @@last_conn == nil
47
+ raise 'No name given and there was no previous MySQL connection to use'
48
+ end
49
+
50
+ MySqlQuery.new(query).instance_eval(&block) if block_given?
51
+
52
+ if name != nil
53
+ @@last_conn = {
54
+ host: query['host'],
55
+ username: query['username'],
56
+ password: query['password'],
57
+ database: query['database']
58
+ }
59
+ end
60
+
61
+ @@logger.info "Connecting to database #{query['username']}@#{query['host']}:#{query['database']}"
62
+
63
+ client = ::Mysql2::Client.new(**@@last_conn)
64
+
65
+ res = []
66
+
67
+ query['query'].each do |statement|
68
+ @@logger.info 'Executing statement "' + statement + '"'
69
+ res = client.query(statement, cast_booleans: true)
70
+ end if query['query']
71
+
72
+ @@result = res.map { |row| OpenStruct.new row } if res
73
+
74
+ client.close
75
+ end
76
+
77
+ def result
78
+ raise 'No MySQL query has been executed yet' unless @@result
79
+ @@result
80
+ end
81
+ end
82
+
83
+ Spectre.register do |config|
84
+ @@logger = ::Logger.new config['log_file'], progname: 'spectre/mysql'
85
+
86
+ if config.has_key? 'mysql'
87
+ @@mysql_cfg = {}
88
+
89
+ config['mysql'].each do |name, cfg|
90
+ @@mysql_cfg[name] = cfg
91
+ end
92
+ end
93
+ end
94
+
95
+ Spectre.delegate :mysql, :result, to: self
96
+ end
97
+ end
@@ -0,0 +1,103 @@
1
+ module Spectre::Reporter
2
+ class Console
3
+ def initialize config
4
+ @config = config
5
+ end
6
+
7
+ def report run_infos
8
+
9
+ report_str = ''
10
+
11
+ errors = 0
12
+ failures = 0
13
+ skipped = run_infos.select { |x| x.skipped? }.count
14
+
15
+ run_infos
16
+ .select { |x| x.error != nil or x.failure != nil }
17
+ .each_with_index do |run_info, index|
18
+
19
+ spec = run_info.spec
20
+
21
+ report_str += "\n#{index+1}) #{format_title(run_info)}\n"
22
+
23
+ if run_info.failure
24
+ report_str += " Expected #{run_info.failure.expectation}"
25
+ report_str += " with #{run_info.data}" if run_info.data
26
+ report_str += " during #{spec.context.__desc}" if spec.context.__desc
27
+
28
+ report_str += " but it failed"
29
+
30
+ if run_info.failure.cause
31
+ report_str += "\n with an unexpected error:\n"
32
+ report_str += format_exception(run_info.failure.cause)
33
+
34
+ elsif run_info.failure.message and not run_info.failure.message.empty?
35
+ report_str += " with:\n #{run_info.failure.message}"
36
+
37
+ else
38
+ report_str += '.'
39
+ end
40
+
41
+ report_str += "\n"
42
+ failures += 1
43
+
44
+ else
45
+ report_str += " but an unexpected error occured during run\n"
46
+ report_str += format_exception(run_info.error)
47
+ errors += 1
48
+ end
49
+ end
50
+
51
+ if failures + errors > 0
52
+ summary = ''
53
+ summary += "#{run_infos.length - failures - errors - skipped} succeeded "
54
+ summary += "#{failures} failures " if failures > 0
55
+ summary += "#{errors} errors " if errors > 0
56
+ summary += "#{skipped} skipped " if skipped > 0
57
+ summary += "#{run_infos.length} total"
58
+ print "\n#{summary}\n".red
59
+ else
60
+ summary = ''
61
+ summary = "\nRun finished successfully"
62
+ summary += " (#{skipped} skipped)" if skipped > 0
63
+ print "#{summary}\n".green
64
+ end
65
+
66
+ puts report_str.red
67
+ end
68
+
69
+ private
70
+
71
+ def format_title run_info
72
+ title = run_info.spec.subject.desc
73
+ title += ' ' + run_info.spec.desc
74
+ title += " (#{'%.3f' % run_info.duration}s)"
75
+ title += " [#{run_info.spec.name}]"
76
+ title
77
+ end
78
+
79
+ def format_exception error
80
+ non_spectre_files = error.backtrace.select { |x| !x.include? 'lib/spectre' }
81
+
82
+ if non_spectre_files.count > 0
83
+ causing_file = non_spectre_files.first
84
+ else
85
+ causing_file = error.backtrace[0]
86
+ end
87
+
88
+ matches = causing_file.match(/(.*\.rb):(\d+)/)
89
+
90
+ return '' unless matches
91
+
92
+ file, line = matches.captures
93
+ file.slice!(Dir.pwd + '/')
94
+
95
+ str = ''
96
+ str += " file.....: #{file}\n"
97
+ str += " line.....: #{line}\n"
98
+ str += " type.....: #{error.class}\n"
99
+ str += " message..: #{error.message}\n"
100
+ str
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,98 @@
1
+ # https://llg.cubic.org/docs/junit/
2
+ # Azure mappings: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-test-results?view=azure-devops&tabs=junit%2Cyaml
3
+
4
+ module Spectre::Reporter
5
+ class JUnit
6
+ def initialize config
7
+ @config = config
8
+ end
9
+
10
+ def report run_infos
11
+ now = Time.now.getutc
12
+ timestamp = now.strftime('%s')
13
+ datetime = now.strftime('%FT%T%:z')
14
+
15
+ xml_str = '<?xml version="1.0" encoding="UTF-8" ?>'
16
+ xml_str += '<testsuites>'
17
+
18
+ suite_id = 0
19
+
20
+ run_infos.group_by { |x| x.spec.subject }.each do |subject, run_infos|
21
+ failures = run_infos.select { |x| x.failure != nil }
22
+ errors = run_infos.select { |x| x.error != nil }
23
+ skipped = run_infos.select { |x| x.skipped? }
24
+
25
+ xml_str += '<testsuite package="' + subject.desc + '" id="' + suite_id.to_s + '" name="' + subject.desc + '" timestamp="' + datetime + '" tests="' + run_infos.count.to_s + '" failures="' + failures.count.to_s + '" errors="' + errors.count.to_s + '" skipped="' + skipped.count.to_s + '">'
26
+ suite_id += 1
27
+
28
+ run_infos.each do |run_info|
29
+ xml_str += '<testcase classname="' + run_info.spec.file.to_s + '" name="' + run_info.spec.desc + '" timestamp="' + run_info.started.to_s + '" time="' + ('%.3f' % run_info.duration) + '">'
30
+
31
+ if run_info.failure and !run_info.failure.cause
32
+ failure_message = "Expected #{run_info.failure.expectation}"
33
+ failure_message += " with #{run_info.data}" if run_info.data
34
+
35
+ if run_info.failure.message
36
+ failure_message += " but it failed with #{run_info.failure.message}"
37
+ else
38
+ failure_message += " but it failed"
39
+ end
40
+
41
+ xml_str += '<failure message="' + failure_message.gsub('"', '`') + '"></failure>'
42
+ end
43
+
44
+
45
+ if run_info.error or (run_info.failure and run_info.failure.cause)
46
+ error = run_info.error || run_info.failure.cause
47
+
48
+ type = error.class.name
49
+ failure_message = error.message
50
+ text = error.backtrace.join "\n"
51
+
52
+ xml_str += '<error message="' + failure_message.gsub('"', '`') + '" type="' + type + '">'
53
+ xml_str += '<![CDATA[' + text + ']]>'
54
+ xml_str += '</error>'
55
+ end
56
+
57
+
58
+ if run_info.log.count > 0 or run_info.properties.count > 0 or run_info.data
59
+ xml_str += '<system-out>'
60
+
61
+ if run_info.properties.count > 0
62
+ run_info.properties.each do |key, val|
63
+ xml_str += "#{key}: #{val}\n"
64
+ end
65
+ end
66
+
67
+ if run_info.data
68
+ data_str = run_info.data
69
+ data_str = run_info.data.inspect unless run_info.data.is_a? String or run_info.data.is_a? Integer
70
+ xml_str += "data: #{data_str}\n"
71
+ end
72
+
73
+ if run_info.log.count > 0
74
+ messages = run_info.log.map { |x| "[#{x[0].strftime('%F %T')}] #{x[1]}" }
75
+ xml_str += messages.join("\n")
76
+ end
77
+
78
+ xml_str += '</system-out>'
79
+ end
80
+
81
+ xml_str += '</testcase>'
82
+ end
83
+
84
+ xml_str += '</testsuite>'
85
+ end
86
+
87
+ xml_str += '</testsuites>'
88
+
89
+ Dir.mkdir @config['out_path'] if not Dir.exist? @config['out_path']
90
+
91
+ file_path = File.join(@config['out_path'], "spectre-junit_#{timestamp}.xml")
92
+
93
+ File.open(file_path, 'w') do |file|
94
+ file.write(xml_str)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,46 @@
1
+ require 'ostruct'
2
+
3
+ module Spectre
4
+ module Resources
5
+ class ResourceCollection
6
+ def initialize
7
+ @items = {}
8
+ end
9
+
10
+ def add name, path
11
+ @items[name] = path
12
+ end
13
+
14
+ def [] name
15
+ raise "Resource with name '#{name}' does not exist" if not @items.has_key? name
16
+ @items[name]
17
+ end
18
+ end
19
+
20
+ class << self
21
+ @@resources = ResourceCollection.new
22
+
23
+ def resources
24
+ @@resources
25
+ end
26
+ end
27
+
28
+ Spectre.register do |config|
29
+ return if !config.has_key? 'resource_paths'
30
+
31
+ config['resource_paths'].each do |resource_path|
32
+ resource_files = Dir.glob File.join(resource_path, '**/*')
33
+
34
+ resource_files.each do |file|
35
+ file.slice! resource_path
36
+ file = file[1..-1]
37
+ @@resources.add file, File.expand_path(File.join resource_path, file)
38
+ end
39
+ end
40
+
41
+ @@resources.freeze
42
+ end
43
+
44
+ Spectre.delegate :resources, to: Resources
45
+ end
46
+ end
@@ -0,0 +1,149 @@
1
+ require 'net/ssh'
2
+ require 'logger'
3
+
4
+
5
+ module Spectre
6
+ module SSH
7
+ @@cfg = {}
8
+
9
+ class SSHConnection < DslClass
10
+ def initialize host, username, opts, logger
11
+ opts[:non_interactive] = true
12
+
13
+ @__logger = logger
14
+ @__host = host
15
+ @__username = username
16
+ @__opts = opts
17
+ @__session = nil
18
+ @__exit_code = nil
19
+ @__output = ''
20
+ end
21
+
22
+ def file_exists path
23
+ exec "ls #{path}"
24
+ exit_code == 0
25
+ end
26
+
27
+ def owner_of path
28
+ exec "stat -c %U #{path}"
29
+ output.chomp
30
+ end
31
+
32
+ def connect!
33
+ return unless @__session == nil or @__session.closed?
34
+ @__session = Net::SSH.start(@__host, @__username, @__opts)
35
+ end
36
+
37
+ def close
38
+ return unless @__session and not @__session.closed?
39
+ @__session.close
40
+ end
41
+
42
+ def can_connect?
43
+ @__output = nil
44
+
45
+ begin
46
+ connect!
47
+ @__session.open_channel.close
48
+ @__output = "successfully connected to #{@__host} with user #{@__username}"
49
+ @__exit_code = 0
50
+ return true
51
+ rescue Exception => e
52
+ @__logger.error e.message
53
+ @__output = "unable to connect to #{@__host} with user #{@__username}"
54
+ @__exit_code = 1
55
+ end
56
+
57
+ return false
58
+ end
59
+
60
+ def exec command
61
+ connect!
62
+
63
+ log_str = "#{@__session.options[:user]}@#{@__session.host} -p #{@__session.options[:port]} #{command}"
64
+
65
+ @channel = @__session.open_channel do |channel|
66
+ channel.exec(command) do |ch, success|
67
+ abort "could not execute #{command} on #{@__session.host}" unless success
68
+
69
+ @__output = ''
70
+
71
+ channel.on_data do |ch, data|
72
+ @__output += data
73
+ end
74
+
75
+ channel.on_extended_data do |ch,type,data|
76
+ @__output += data
77
+ end
78
+
79
+ channel.on_request('exit-status') do |ch, data|
80
+ @__exit_code = data.read_long
81
+ end
82
+
83
+ # channel.on_request('exit-signal') do |ch, data|
84
+ # exit_code = data.read_long
85
+ # end
86
+ end
87
+
88
+ end
89
+
90
+ @channel.wait
91
+ @__session.loop
92
+
93
+ log_str += "\n" + @__output
94
+ @__logger.info log_str
95
+ end
96
+
97
+ def output
98
+ @__output
99
+ end
100
+
101
+ def exit_code
102
+ @__exit_code
103
+ end
104
+ end
105
+
106
+
107
+ class << self
108
+ def ssh name, config = {}, &block
109
+ raise "SSH connection '#{name}' not configured" unless @@cfg.has_key?(name) or config.count > 0
110
+
111
+ cfg = @@cfg[name] || {}
112
+
113
+ host = cfg['host'] || name
114
+ username = config[:username] || cfg['username']
115
+ password = config[:password] || cfg['password']
116
+
117
+ opts = {}
118
+ opts[:password] = password
119
+ opts[:port] = config[:port] || cfg['port'] || 22
120
+ opts[:keys] = [cfg['key']] if cfg.has_key? 'key'
121
+ opts[:passphrase] = cfg['passphrase'] if cfg.has_key? 'passphrase'
122
+
123
+ opts[:auth_methods] = []
124
+ opts[:auth_methods].push 'publickey' if opts[:keys]
125
+ opts[:auth_methods].push 'password' if opts[:password]
126
+
127
+ ssh_con = SSHConnection.new(host, username, opts, @@logger)
128
+
129
+ begin
130
+ ssh_con.instance_eval &block
131
+ ensure
132
+ ssh_con.close
133
+ end
134
+ end
135
+ end
136
+
137
+ Spectre.register do |config|
138
+ @@logger = ::Logger.new config['log_file'], progname: 'spectre/ssh'
139
+
140
+ if config.has_key? 'ssh'
141
+ config['ssh'].each do |name, cfg|
142
+ @@cfg[name] = cfg
143
+ end
144
+ end
145
+ end
146
+
147
+ Spectre.delegate :ssh, to: self
148
+ end
149
+ end