cem_win_spec 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,33 @@
1
+ # CemWinSpec
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/cem_win_spec`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add cem_win_spec
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install cem_win_spec
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cem_win_spec.
30
+
31
+ ## License
32
+
33
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/cem_win_spec/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "cem_win_spec"
7
+ spec.version = CemWinSpec::VERSION
8
+ spec.authors = ["Heston Snodgrass"]
9
+ spec.email = ["hsnodgrass@users.noreply.github.com"]
10
+
11
+ spec.summary = "Write a short summary, because RubyGems requires one."
12
+ spec.description = "Write a longer description or delete this line."
13
+ spec.homepage = "https://github.com/hsnodgrass/cem_win_spec"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = ["cem-win-spec"]
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "winrm", "~> 2.3"
32
+ spec.add_dependency "winrm-fs", "~> 1.3"
33
+ spec.add_dependency "tty-spinner", "~> 0.9"
34
+ spec.add_dependency "puppet_forge", "~> 4.1"
35
+ spec.add_dependency "parallel_tests", "~> 3.4"
36
+ spec.add_development_dependency "pry"
37
+ end
data/exe/cem-win-spec ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'cem_win_spec'
6
+
7
+ # This is a wrapper script for the cem_win_spec gem. It is used to run the
8
+ # cem_win_spec gem from the command line. It is installed as part of the gem
9
+ # and is not intended to be run directly from the source code.
10
+
11
+ # Parse command line options
12
+ options = {}
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = 'Usage: cem-win-spec [options]'
15
+
16
+ opts.on('-v', '--version', 'Print version and exit') do
17
+ puts "cem-win-spec version #{CemWinSpec::VERSION}"
18
+ exit 0
19
+ end
20
+
21
+ opts.on('-q', '--quiet', 'Suppress output') do
22
+ options[:quiet] = true
23
+ end
24
+
25
+ opts.on('-d', '--debug', 'Enable debug output on the console') do
26
+ options[:debug] = true
27
+ end
28
+
29
+ opts.on('-V', '--verbose', 'Enable verbose output on the console') do
30
+ options[:verbose] = true
31
+ end
32
+
33
+ opts.on('-l', '--log-level [LEVEL]', 'Set log level (debug, info, warn, error, fatal)') do |level|
34
+ options[:log_level] = level
35
+ end
36
+
37
+ opts.on('-L', '--log-file [FILE]', 'Log output to file') do |file|
38
+ options[:log_file] = file
39
+ end
40
+
41
+ opts.on('-f', '--log-format [FORMAT]', 'Set log format(file, text, json, github_action') do |log_format|
42
+ options[:log_format] = log_format
43
+ end
44
+
45
+ opts.on('-h', '--help', 'Print this help') do
46
+ puts opts
47
+ exit 0
48
+ end
49
+ end
50
+ parser.parse!
51
+
52
+ CemWinSpec.run_tests(options)
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require 'pathname'
7
+ require 'puppet_forge'
8
+ require 'yaml'
9
+
10
+ module CemWinSpec
11
+ # Class for managing cached fixtures
12
+ # Fixture caching is used to speed up test runs by reusing the same
13
+ # Puppet modules between test runs instead of downloading them each time.
14
+ # The cache works by creating a YAML file that maps module names and
15
+ class FixtureCache
16
+ CACHE_MANIFEST = 'cache_manifest.yaml'
17
+
18
+ attr_reader :cache_dir, :cache_entries
19
+
20
+ def initialize(cache_dir = 'C:\\ProgramData\\cem_win_spec')
21
+ raise 'FixtureCache must be ran on Windows' unless Gem.win_platform?
22
+
23
+ @cache_dir = cache_dir
24
+ @cache_entries = setup_and_load_cache
25
+ @dependencies = dependencies_from_metadata
26
+ setup!
27
+ end
28
+
29
+ def copy_fixtures_to(fixtures_dir)
30
+ @cache_entries.each do |_, path|
31
+ FileUtils.cp_r(path, fixtures_dir)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def setup!
38
+ @dependencies.each do |name, data|
39
+ next if cached?(data[:checksum])
40
+
41
+ puts "Downloading #{name} #{data[:version]}..."
42
+ download_and_cache(name, data[:release_slug], data[:checksum])
43
+ end
44
+ ensure
45
+ save_cache_entries
46
+ end
47
+
48
+ def dependencies_from_metadata
49
+ raise "File metadata.json not found in current directory #{Dir.pwd}" unless File.exist?('metadata.json')
50
+
51
+ metadata = JSON.parse(File.read('metadata.json'))
52
+ return {} unless metadata.key?('dependencies')
53
+
54
+ metadata['dependencies'].each_with_object({}) do |dep, hsh|
55
+ mod_name = dep['name'].tr('/', '-')
56
+ latest_valid_release = PuppetForge::Module.find(mod_name).releases.find do |r|
57
+ dep_version_req_satisfied?(dep['version_requirement'], r.version)
58
+ end
59
+
60
+ hsh[mod_name] = {
61
+ checksum: module_checksum(mod_name, dep['version_requirement'], latest_valid_release.version),
62
+ version_req: dep['version_requirement'],
63
+ version: latest_valid_release.version,
64
+ release_slug: latest_valid_release.slug,
65
+ }
66
+ end
67
+ end
68
+
69
+ def dep_version_req_satisfied?(version_req, version)
70
+ reqs = version_req.match(%r{^([<>=!]+ \S+)\s*([<>=!]+ \S+)})
71
+ raise "Invalid version requirement #{version_req}" unless reqs.to_a.length > 1
72
+
73
+ reqs.to_a[1..-1].all? { |req| version_req_satisfied?(req, version) }
74
+ end
75
+
76
+ def version_req_satisfied?(version_req, version)
77
+ Gem::Requirement.create(version_req).satisfied_by?(Gem::Version.new(version))
78
+ end
79
+
80
+ def module_checksum(name, version_req, version)
81
+ Digest::SHA256.hexdigest("#{name}#{version_req}#{version}")
82
+ end
83
+
84
+ def manifest
85
+ File.join(cache_dir, CACHE_MANIFEST)
86
+ end
87
+
88
+ def setup_and_load_cache
89
+ unless Dir.exist?(cache_dir)
90
+ puts "Creating cache directory #{cache_dir}..."
91
+ FileUtils.mkdir_p(cache_dir)
92
+ end
93
+ unless File.exist?(manifest)
94
+ puts "Creating cache manifest #{manifest}..."
95
+ File.write(manifest, {}.to_yaml)
96
+ end
97
+ YAML.load_file(manifest)
98
+ end
99
+
100
+ def cached?(checksum)
101
+ cache_entries.key?(checksum)
102
+ end
103
+
104
+ def download_and_cache(release_slug, checksum)
105
+ release = PuppetForge::Release.find(release_slug)
106
+ module_cache_dir = File.join(cache_dir, checksum)
107
+ ::FileUtils.mkdir_p(File.join(cache_dir, checksum))
108
+ tarball_path = File.join(cache_dir, checksum, "#{release_slug}.tar.gz")
109
+ release.download(Pathname(tarball_path))
110
+ release.verify(Pathname(tarball_path))
111
+ PuppetForge::Unpacker.unpack(tarball_path,
112
+ File.join(module_cache_dir, name),
113
+ File.join(module_cache_dir, 'temp'))
114
+ cache_entries[checksum] = File.join(module_cache_dir, name)
115
+ end
116
+
117
+ def save_cache_entries
118
+ File.write(manifest, cache_entries.to_yaml)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+ require_relative 'logging'
5
+
6
+ module CemWinSpec
7
+ # This class is used to create a tunnel to a GCP instance
8
+ class IapTunnel
9
+ include CemWinSpec::Logging
10
+
11
+ # We don't go all the way to 65_535 because gcloud shits the bed
12
+ # on MacOS when you assign port 65_535 to a tunnel for some reason.
13
+ EPHEMERAL_PORT_RANGE = (49_152..65_534).to_a.freeze
14
+
15
+ attr_reader :pid, :port, :project_id, :zone, :instance_name
16
+
17
+ def initialize(project_id = nil, zone = nil, instance_name = nil)
18
+ @project_id = project_id || get_project_id
19
+ @zone = zone || get_zone
20
+ @instance_name = instance_name || get_instance_name
21
+ @port = find_available_port # Get a random port from the ephemeral port range
22
+ @pid = nil
23
+ end
24
+
25
+ def running?
26
+ !@pid.nil?
27
+ end
28
+
29
+ def with
30
+ return unless block_given?
31
+
32
+ start
33
+ yield port
34
+ ensure
35
+ stop
36
+ end
37
+
38
+ def start
39
+ return if running?
40
+
41
+ logger.info 'Starting IAP tunnel...'
42
+ logger.debug "Running command: #{tunnel_cmd}"
43
+ @pid = spawn(tunnel_cmd)
44
+ sleep(5)
45
+ end
46
+
47
+ # This method stops the IAP tunnel
48
+ # @param wait [Boolean] Whether to wait for the tunnel to stop before returning
49
+ # @param log [Boolean] Whether to log messages. This must be set to false if you are
50
+ # calling this method from within a trap block, otherwise the logger will throw an exception.
51
+ def stop(wait: true, log: true)
52
+ return unless running?
53
+
54
+ logger.info 'Stopping IAP tunnel...' if log
55
+ logger.debug "Killing PID: #{@pid}" if log
56
+ Process.kill('TERM', @pid)
57
+ Process.waitpid(@pid) if wait
58
+ @pid = nil
59
+ logger.info 'IAP tunnel stopped' if log
60
+ end
61
+
62
+ private
63
+
64
+ def tunnel_cmd
65
+ raise 'Port must be set' unless port
66
+ [
67
+ 'gcloud',
68
+ 'compute',
69
+ 'start-iap-tunnel',
70
+ instance_name,
71
+ '5986',
72
+ "--local-host-port=localhost:#{port}",
73
+ "--zone=#{zone}",
74
+ "--project=#{project_id}",
75
+ ].join(' ')
76
+ end
77
+
78
+ # This function gets the GCP project ID
79
+ def get_project_id
80
+ # Get the project ID from the environment
81
+ project_id = ENV['GCP_PROJECT_ID'] || `gcloud config get-value project`.chomp
82
+
83
+ # If the project ID is not set, prompt the user for it
84
+ if project_id.nil? && ENV['CI']
85
+ raise 'Please set the GCP_PROJECT_ID environment variable or ensure that gcloud is configured'
86
+ elsif project_id.nil?
87
+ puts 'Please enter your GCP project ID:'
88
+ project_id = $stdin.cooked(&:gets).chomp
89
+ end
90
+
91
+ raise 'GCP project ID cannot be empty' if project_id.empty?
92
+
93
+ logger.debug 'GCP project ID is set'
94
+ # Return the project ID
95
+ project_id
96
+ end
97
+
98
+ # This function gets the GCP zone
99
+ def get_zone
100
+ return @zone if @zone
101
+
102
+ # Get the zone from the environment
103
+ zone = ENV['GCP_ZONE'] || `gcloud config get-value compute/zone`.chomp
104
+
105
+ # If the zone is not set, prompt the user for it
106
+ if zone.nil? && ENV['CI']
107
+ raise 'Please set the GCP_ZONE environment variable or ensure that gcloud is configured'
108
+ elsif zone.nil?
109
+ puts 'Please enter your GCP zone:'
110
+ zone = $stdin.cooked(&:gets).chomp
111
+ end
112
+
113
+ raise 'GCP zone cannot be empty' if zone.empty?
114
+
115
+ logger.debug 'GCP zone is set'
116
+ # Return the zone
117
+ zone
118
+ end
119
+
120
+ # This function gets the GCP instance name
121
+ def get_instance_name
122
+ # Get the instance name from the environment
123
+ instance_name = ENV['GCP_INSTANCE_NAME']
124
+
125
+ # If the instance name is not set, prompt the user for it
126
+ if instance_name.nil? && ENV['CI']
127
+ raise 'Please set the GCP_INSTANCE_NAME environment variable'
128
+ elsif instance_name.nil?
129
+ puts 'Please enter your GCP instance name:'
130
+ instance_name = $stdin.cooked(&:gets).chomp
131
+ end
132
+
133
+ raise 'GCP instance name cannot be empty' if instance_name.empty?
134
+
135
+ logger.debug 'GCP instance name is set'
136
+ # Return the instance name
137
+ instance_name
138
+ end
139
+
140
+ # This function ensures that gcloud is installed, usable, and authenticated
141
+ def gcloud_setup
142
+ # Ensure that gcloud is installed
143
+ unless system('gcloud --version')
144
+ raise 'Please install Google Cloud SDK'
145
+ end
146
+
147
+ # Ensure that gcloud is authenticated
148
+ unless system('gcloud auth list --format=json | grep ACTIVE')
149
+ raise 'Please run `gcloud auth login`'
150
+ end
151
+ end
152
+
153
+ # This function finds an available local port to use for the IAP tunnel
154
+ def find_available_port
155
+ port_range = EPHEMERAL_PORT_RANGE.dup.shuffle
156
+ port_range.each do |prt|
157
+ return prt if local_port_open?(prt)
158
+ end
159
+ raise 'No available ports found'
160
+ end
161
+
162
+ # This function checks if a local port is open
163
+ def local_port_open?(prt)
164
+ require 'socket'
165
+ socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
166
+ socket.bind(Socket.pack_sockaddr_in(prt, '0.0.0.0'))
167
+ true
168
+ rescue Errno::EADDRINUSE, Errno::CONNREFUSED
169
+ false
170
+ ensure
171
+ socket&.close
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemWinSpec
4
+ module Logging
5
+ module Formatter
6
+ class << self
7
+ def for(log_format)
8
+ log_format = log_format.to_s.downcase.to_sym if log_format.respond_to?(:to_s)
9
+ all.find { |f| f.log_format == log_format }.get
10
+ end
11
+
12
+ private
13
+
14
+ def all
15
+ @all ||= [FileFormatter.new, JSONFormatter.new, TextFormatter.new, GithubActionFormatter.new]
16
+ end
17
+ end
18
+
19
+ class FileFormatter
20
+ attr_reader :log_format
21
+
22
+ def initialize
23
+ @log_format = :file
24
+ end
25
+
26
+ def get
27
+ proc do |severity, datetime, progname, msg|
28
+ format(severity, datetime, progname, msg)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def format(severity, datetime, progname, msg)
35
+ "[#{datetime}] | #{progname} | #{severity} | #{msg}\n"
36
+ end
37
+ end
38
+
39
+ class JSONFormatter < FileFormatter
40
+ def initialize
41
+ super
42
+ @log_format = :json
43
+ end
44
+
45
+ private
46
+
47
+ def format(severity, datetime, progname, msg)
48
+ require 'json'
49
+ {
50
+ timestamp: datetime,
51
+ progname: progname,
52
+ severity: severity,
53
+ message: msg,
54
+ }.to_json + "\n"
55
+ end
56
+ end
57
+
58
+ class TextFormatter < FileFormatter
59
+ def initialize
60
+ super
61
+ @log_format = :text
62
+ end
63
+
64
+ private
65
+
66
+ def format(severity, _datetime, _progname, msg)
67
+ severity == 'INFO' ? "#{msg}\n" : "#{severity}: #{msg}\n"
68
+ end
69
+ end
70
+
71
+ class GithubActionFormatter < FileFormatter
72
+ SEV_MAP = {
73
+ 'DEBUG' => '::debug',
74
+ 'INFO' => '::notice',
75
+ 'WARN' => '::warning',
76
+ 'ERROR' => '::error',
77
+ 'FATAL' => '::error',
78
+ }.freeze
79
+
80
+ def initialize
81
+ super
82
+ @log_format = :github_action
83
+ end
84
+
85
+ private
86
+
87
+ def format(severity, _datetime, _progname, msg)
88
+ if severity == 'DEBUG'
89
+ "#{SEV_MAP[severity]}::{#{msg}}\n"
90
+ else
91
+ "#{SEV_MAP[severity]} #{msg}\n"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require_relative 'logging/formatter'
5
+
6
+ module CemWinSpec
7
+ # Logging for CemWinSpec
8
+ module Logging
9
+ LEVEL_MAP = {
10
+ 'debug' => Logger::DEBUG,
11
+ 'info' => Logger::INFO,
12
+ 'warn' => Logger::WARN,
13
+ 'error' => Logger::ERROR,
14
+ 'fatal' => Logger::FATAL,
15
+ 'unknown' => Logger::UNKNOWN,
16
+ }
17
+
18
+ # Delegator class for when you want to log to multiple devices
19
+ # at the same time, such as STDOUT and a file.
20
+ # @param loggers [::Logger] one or more instances of Logger
21
+ class MultiLogger
22
+ def initialize(*loggers)
23
+ @loggers = loggers
24
+ end
25
+
26
+ def method_missing(m, *args, &block)
27
+ if @loggers.all? { |l| l.respond_to?(m) }
28
+ @loggers.map { |l| l.send(m, *args, &block) }
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def respond_to_missing?(m, include_private = false)
35
+ @loggers.all? { |l| l.respond_to?(m) } || super
36
+ end
37
+ end
38
+
39
+ class << self
40
+ def log_setup!(options = {})
41
+ console_l_level = if options[:debug]
42
+ new_log_level('debug')
43
+ else
44
+ new_log_level('info')
45
+ end
46
+ console_l_formatter = if ENV['CI'] || ENV['GITHUB_ACTIONS']
47
+ new_log_formatter(options[:log_format] || 'github_action')
48
+ else
49
+ new_log_formatter(options[:log_format] || 'text')
50
+ end
51
+ file_l_level = new_log_level(options[:log_level] || 'debug')
52
+ file_l_formatter = new_log_formatter(options[:log_format] || 'file')
53
+ loggers = []
54
+ if options[:log_file]
55
+ loggers << ::Logger.new(
56
+ options[:log_file],
57
+ level: file_l_level,
58
+ formatter: file_l_formatter,
59
+ progname: 'CemWinSpec',
60
+ datetime_format: '%Y%m%dT%H%M%S%z',
61
+ )
62
+ end
63
+ unless options[:quiet]
64
+ loggers << ::Logger.new(
65
+ $stdout,
66
+ level: console_l_level,
67
+ formatter: console_l_formatter,
68
+ progname: 'CemWinSpec',
69
+ datetime_format: '%Y%m%dT%H%M%S%z',
70
+ )
71
+ end
72
+ @logger = MultiLogger.new(*loggers)
73
+ end
74
+
75
+ # Exposes a logger instance. Will either use the currently set logger or
76
+ # create a new one.
77
+ # @return [Logger]
78
+ def logger
79
+ raise 'Logger not set up! Call CemWinSpec::Logging.log_setup! first.' unless @logger
80
+
81
+ @logger
82
+ end
83
+
84
+ # Shortcut method for logger.level
85
+ # @return [Logger::Severity]
86
+ def current_log_level
87
+ logger.level
88
+ end
89
+
90
+ # Gets a log level from a string and returns the corresponding
91
+ # Logger::Severity constant.
92
+ # @param level [String] the log level to get
93
+ def new_log_level(level)
94
+ raise ArgumentError, 'Log level not recognized' unless LEVEL_MAP[level.to_s.downcase]
95
+
96
+ LEVEL_MAP[level.downcase]
97
+ end
98
+
99
+ # Shows the current log format style if set, or the default if not.
100
+ # @return [Symbol] the current log format style
101
+ def current_log_format
102
+ @current_log_format ||= :text
103
+ end
104
+
105
+ # Sets the current log format style and returns a proc to be passed to
106
+ # Logger#formatter=
107
+ # @param f [Symbol] the log format style to set
108
+ # @return [Proc] the proc to be passed to Logger#formatter=
109
+ def new_log_formatter(f)
110
+ Formatter.for(f)
111
+ end
112
+ end
113
+
114
+ # Provides class method wrappers for logging methods
115
+ def self.included(base)
116
+ class << base
117
+ def log_setup!(options = {})
118
+ CemWinSpec::Logging.log_setup!(options)
119
+ end
120
+
121
+ def logger
122
+ CemWinSpec::Logging.logger
123
+ end
124
+
125
+ def current_log_level
126
+ CemWinSpec::Logging.current_log_level
127
+ end
128
+
129
+ def new_log_level(level)
130
+ CemWinSpec::Logging.new_log_level(level)
131
+ end
132
+
133
+ def current_log_format
134
+ CemWinSpec::Logging.current_log_format
135
+ end
136
+
137
+ def new_log_formatter(f)
138
+ CemWinSpec::Logging.new_log_formatter(f)
139
+ end
140
+ end
141
+ end
142
+
143
+ def log_setup!(options = {})
144
+ CemWinSpec::Logging.log_setup!(options)
145
+ end
146
+
147
+ # Exposes the logger instance
148
+ def logger
149
+ CemWinSpec::Logging.logger
150
+ end
151
+
152
+ # Exposes the current log level
153
+ def current_log_level
154
+ CemWinSpec::Logging.current_log_level
155
+ end
156
+
157
+ # Exposes setting the log level
158
+ def new_log_level(level)
159
+ CemWinSpec::Logging.new_log_level(level)
160
+ end
161
+
162
+ def current_log_format
163
+ CemWinSpec::Logging.current_log_format
164
+ end
165
+
166
+ def new_log_formatter(f)
167
+ CemWinSpec::Logging.new_log_formatter(f)
168
+ end
169
+ end
170
+ end