mirror of
https://github.com/inspec/inspec
synced 2024-12-20 18:13:20 +00:00
423 lines
14 KiB
Ruby
423 lines
14 KiB
Ruby
|
# require 'helper'
|
|||
|
|
|||
|
require 'minitest'
|
|||
|
require 'minitest/spec'
|
|||
|
require 'inspec/ui'
|
|||
|
require 'inspec/base_cli'
|
|||
|
require 'inspec/errors'
|
|||
|
require 'stringio'
|
|||
|
|
|||
|
# https://gist.github.com/chrisopedia/8754917
|
|||
|
# http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
|||
|
ANSI_CODES = {
|
|||
|
reset: "\e[0m",
|
|||
|
bold: "\e[1m",
|
|||
|
color: {
|
|||
|
red: "\e[38;5;9m", # 256-color light red
|
|||
|
green: "\e[38;5;41m", # 256-color light green
|
|||
|
yellow: "\e[33m",
|
|||
|
cyan: "\e[36m",
|
|||
|
white: "\e[37m",
|
|||
|
grey: "\e[38;5;247m", # 256-color medium grey
|
|||
|
},
|
|||
|
}.freeze
|
|||
|
|
|||
|
GLYPHS = {
|
|||
|
bullet: '•', # BULLET, Unicode: U+2022, UTF-8: E2 80 A2
|
|||
|
check: '✔', # HEAVY CHECK MARK, Unicode: U+2714, UTF-8: E2 9C 94
|
|||
|
swirl: '↺', # ANTICLOCKWISE OPEN CIRCLE ARROW, Unicode U+21BA, UTF-8: E2 86 BA
|
|||
|
script_x: '×', # MULTIPLICATION SIGN, Unicode: U+00D7, UTF-8: C3 97
|
|||
|
question: '?', # normal ASCII question mark
|
|||
|
em_dash: '─', # BOX DRAWINGS LIGHT HORIZONTAL Unicode: U+2500, UTF-8: E2 94 80
|
|||
|
heavy_dash: '≖', # RING IN EQUAL TO, Unicode: U+2256, UTF-8: E2 89 96
|
|||
|
vertical_dash: '│', # │ BOX DRAWINGS LIGHT VERTICAL, Unicode: U+2502, UTF-8: E2 94 82
|
|||
|
table_corner: '⨀', # N-ARY CIRCLED DOT OPERATOR, Unicode: U+2A00, UTF-8: E2 A8 80
|
|||
|
}.freeze
|
|||
|
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# Low-Level Formatting
|
|||
|
#=============================================================================#
|
|||
|
describe 'Inspec::UI low-level Formatting' do
|
|||
|
let(:fixture_io) { StringIO.new() }
|
|||
|
let(:output) { fixture_io.string }
|
|||
|
let(:ui) { Inspec::UI.new(io: fixture_io) }
|
|||
|
|
|||
|
describe 'plain' do
|
|||
|
it 'uses no ANSI markers' do
|
|||
|
ui.plain('test')
|
|||
|
output.must_include('test')
|
|||
|
output.wont_include('\e[')
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'when color is enabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
|
|||
|
|
|||
|
describe 'bold' do
|
|||
|
it 'uses ANSI bold markers' do
|
|||
|
ui.bold('test')
|
|||
|
output.must_equal(ANSI_CODES[:bold] + 'test' + ANSI_CODES[:reset])
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'colors' do
|
|||
|
[:red, :green, :cyan, :yellow, :white, :grey].each do |color|
|
|||
|
it ('uses the color code for ' + color.to_s) do
|
|||
|
ui.send(color, 'test')
|
|||
|
output.must_equal(ANSI_CODES[:color][color] + 'test' + ANSI_CODES[:reset])
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'when color is disabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
|
|||
|
describe 'bold' do
|
|||
|
it 'uses no ANSI codes' do
|
|||
|
ui.bold('test')
|
|||
|
output.wont_include('\e[')
|
|||
|
output.must_equal('test')
|
|||
|
end
|
|||
|
end
|
|||
|
describe 'colors' do
|
|||
|
[:red, :green, :yellow, :white, :grey].each do |color|
|
|||
|
it ('uses no ANSI codes for ' + color.to_s) do
|
|||
|
ui.send(color, 'test')
|
|||
|
output.wont_include('\e[')
|
|||
|
output.must_equal('test')
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# High-Level Formatting
|
|||
|
#=============================================================================#
|
|||
|
describe 'Inspec::UI High-Level Formatting' do
|
|||
|
let(:fixture_io) { StringIO.new() }
|
|||
|
let(:output) { fixture_io.string }
|
|||
|
|
|||
|
describe 'when color is enabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
|
|||
|
|
|||
|
describe 'emphasis' do
|
|||
|
it 'uses ANSI escapes' do
|
|||
|
ui.emphasis('test')
|
|||
|
output.must_equal(ANSI_CODES[:color][:cyan] + 'test' + ANSI_CODES[:reset])
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'headline' do
|
|||
|
it 'formats the headline when short' do
|
|||
|
ui.headline('test')
|
|||
|
output.must_match(/^\n/) # Start with one newlines
|
|||
|
expected = ''
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'test' + ANSI_CODES[:reset]
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n\n$/) # End with two newline
|
|||
|
end
|
|||
|
it 'formats the headline when longer' do
|
|||
|
ui.headline('Testing is Such a Pleasure!')
|
|||
|
expected = ''
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 24 + ' '
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'Testing is Such a Pleasure!' + ANSI_CODES[:reset]
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 24 + ' '
|
|||
|
output.must_include(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'error' do
|
|||
|
it 'formats the message' do
|
|||
|
ui.error('Everything has gone terribly wrong')
|
|||
|
expected = ''
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:red]
|
|||
|
expected += 'ERROR:'
|
|||
|
expected += ANSI_CODES[:reset]
|
|||
|
expected += ' '
|
|||
|
expected += 'Everything has gone terribly wrong'
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n$/) # End with a newline
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'warning' do
|
|||
|
it 'formats the message' do
|
|||
|
ui.warning('Maybe we can still pull through this')
|
|||
|
expected = ''
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:yellow]
|
|||
|
expected += 'WARNING:'
|
|||
|
expected += ANSI_CODES[:reset]
|
|||
|
expected += ' '
|
|||
|
expected += 'Maybe we can still pull through this'
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n$/) # End with a newline
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
end
|
|||
|
|
|||
|
describe 'when color is disabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
|
|||
|
|
|||
|
describe 'emphasis' do
|
|||
|
it 'does not use ANSI escapes' do
|
|||
|
ui.emphasis('test')
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
output.must_equal('test')
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'headline' do
|
|||
|
it 'formats the headline when short' do
|
|||
|
ui.headline('test')
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
expected = ''
|
|||
|
expected += ' ' + '-' * 36 + ' '
|
|||
|
expected += 'test'
|
|||
|
expected += ' ' + '-' * 36 + ' '
|
|||
|
output.must_include(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'error' do
|
|||
|
it 'formats the message without color' do
|
|||
|
ui.error('Everything has gone terribly wrong')
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
expected = ''
|
|||
|
expected += 'ERROR:'
|
|||
|
expected += ' '
|
|||
|
expected += 'Everything has gone terribly wrong'
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n$/) # End with a newline
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'warning' do
|
|||
|
it 'formats the message' do
|
|||
|
ui.warning('Maybe we can still pull through this')
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
expected = ''
|
|||
|
expected += 'WARNING:'
|
|||
|
expected += ' '
|
|||
|
expected += 'Maybe we can still pull through this'
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n$/) # End with a newline
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# Tables and Lists
|
|||
|
#=============================================================================#
|
|||
|
describe 'Inspec::UI Tables and Lists' do
|
|||
|
let(:fixture_io) { StringIO.new() }
|
|||
|
let(:output) { fixture_io.string }
|
|||
|
|
|||
|
describe 'when color is enabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
|
|||
|
|
|||
|
describe('line') do
|
|||
|
it 'draws a line' do
|
|||
|
ui.line
|
|||
|
expected = ANSI_CODES[:bold] + GLYPHS[:heavy_dash]*80 + ANSI_CODES[:reset] + "\n"
|
|||
|
output.must_equal(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe('list_item') do
|
|||
|
it 'makes a bullet point' do
|
|||
|
ui.list_item('test')
|
|||
|
expected = ' '
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
|
|||
|
expected += GLYPHS[:bullet]
|
|||
|
expected += ANSI_CODES[:reset]
|
|||
|
expected += ' ' + 'test' + "\n"
|
|||
|
output.must_equal(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe('tables') do
|
|||
|
it 'makes a table' do
|
|||
|
ui.table do |t|
|
|||
|
t.header = ['Fruit', 'Tartness', 'Succulence']
|
|||
|
t << ['Dragonfruit', 'Very Low', 'High']
|
|||
|
t << ["The Exquisite Lime, Scurvy's Bane", 'High', 'Medium']
|
|||
|
end
|
|||
|
lines = output.split("\n")
|
|||
|
|
|||
|
# First, third, and last lines should be horizontal dividors
|
|||
|
[0, 2, -1].each do |idx|
|
|||
|
lines[idx].must_include(GLYPHS[:em_dash]*3)
|
|||
|
lines[idx].wont_include(' ')
|
|||
|
end
|
|||
|
|
|||
|
# Second, fourth, and fifth lines should have custom vertical dividors
|
|||
|
[1, 3, 4].each do |idx|
|
|||
|
lines[idx].must_match(/^#{GLYPHS[:vertical_dash]}/) # Start with a vertical line
|
|||
|
lines[idx].must_match(/#{GLYPHS[:vertical_dash]}$/) # End with a vertical line
|
|||
|
end
|
|||
|
|
|||
|
# Second (header) line should have bold and white on each header label
|
|||
|
lines[1].split(GLYPHS[:vertical_dash]).map(&:strip).reject{ |e| e == ""}.each do |header_label|
|
|||
|
header_label.must_include ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
|
|||
|
header_label.must_include ANSI_CODES[:reset]
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'when color is disabled' do
|
|||
|
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
|
|||
|
|
|||
|
describe('line') do
|
|||
|
it 'draws a line without ANSI codes or special glyphs' do
|
|||
|
ui.line
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
expected = '-' * 80 + "\n"
|
|||
|
output.must_equal(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe('list_item') do
|
|||
|
it 'makes a bullet point without ANSI codes or special glyphs' do
|
|||
|
ui.list_item('test')
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
expected = ' ' + '*' + ' ' + 'test' + "\n"
|
|||
|
output.must_equal(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe('tables') do
|
|||
|
it 'makes a table ANSI codes or special glyphs' do
|
|||
|
ui.table do |t|
|
|||
|
t.header = ['Fruit', 'Tartness', 'Succulence']
|
|||
|
t << ['Dragonfruit', 'Very Low', 'High']
|
|||
|
t << ["The Exquisite Lime, Scurvy's Bane", 'High', 'Medium']
|
|||
|
end
|
|||
|
|
|||
|
output.wont_include('\e[') # No ANSI escapes
|
|||
|
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
|
|||
|
|
|||
|
lines = output.split("\n")
|
|||
|
|
|||
|
# First, third, and last lines should be horizontal dividors
|
|||
|
[0, 2, -1].each do |idx|
|
|||
|
lines[idx].must_match(/^\+/) # Start with a corner
|
|||
|
lines[idx].must_match(/\+$/) # End with a corner
|
|||
|
lines[idx].must_match(/\-\+\-/) # Have internal corners
|
|||
|
lines[idx].wont_include(' ')
|
|||
|
end
|
|||
|
|
|||
|
# Second, fourth, and fifth lines should have stock vertical dividors
|
|||
|
[1, 3, 4].each do |idx|
|
|||
|
lines[idx].must_match(/^\|/) # Start with a vertical line
|
|||
|
lines[idx].must_match(/\|$/) # End with a vertical line
|
|||
|
lines[idx].must_match(/\s\|\s/) # Have vertical line
|
|||
|
lines[idx].wont_include('+')
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# CLI Integration
|
|||
|
#=============================================================================#
|
|||
|
describe 'Inspec::UI CLI integration' do
|
|||
|
let(:fixture_io) { StringIO.new() }
|
|||
|
let(:output) { fixture_io.string }
|
|||
|
let(:cli) { Inspec::BaseCLI.new }
|
|||
|
|
|||
|
describe 'ui method' do
|
|||
|
it 'should respond to ui' do
|
|||
|
cli.must_respond_to(:ui)
|
|||
|
cli.must_respond_to(:'ui=')
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'backwards compatibility' do
|
|||
|
it 'should support plain_text' do
|
|||
|
cli.ui = Inspec::UI.new(io: fixture_io)
|
|||
|
cli.plain_text('test')
|
|||
|
output.must_equal "test\n"
|
|||
|
end
|
|||
|
it 'should support mark_text' do
|
|||
|
# mark_text applies cyan and DOES NOT PRINT
|
|||
|
cli.ui = Inspec::UI.new(io: fixture_io)
|
|||
|
result = cli.mark_text('test')
|
|||
|
result.must_equal ANSI_CODES[:color][:cyan] + 'test' + ANSI_CODES[:reset]
|
|||
|
output.must_equal ''
|
|||
|
end
|
|||
|
it 'should support headline' do
|
|||
|
cli.ui = Inspec::UI.new(io: fixture_io)
|
|||
|
cli.headline('test')
|
|||
|
output.must_match(/^\n/) # Start with one newlines
|
|||
|
expected = ''
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'test' + ANSI_CODES[:reset]
|
|||
|
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
|
|||
|
output.must_include(expected)
|
|||
|
output.must_match(/\n\n$/) # End with two newline
|
|||
|
end
|
|||
|
it 'should support li' do
|
|||
|
cli.ui = Inspec::UI.new(io: fixture_io)
|
|||
|
cli.li('test')
|
|||
|
expected = ' '
|
|||
|
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
|
|||
|
expected += GLYPHS[:bullet]
|
|||
|
expected += ANSI_CODES[:reset]
|
|||
|
expected += ' ' + 'test' + "\n"
|
|||
|
output.must_equal(expected)
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# Interactivity
|
|||
|
#=============================================================================#
|
|||
|
describe 'interactivity' do
|
|||
|
describe 'when interactivity is disabled' do
|
|||
|
describe 'interactive check' do
|
|||
|
it "should be false" do
|
|||
|
ui = Inspec::UI.new(interactive: false)
|
|||
|
ui.interactive?.must_equal false
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe 'prompt' do
|
|||
|
it 'should throw an exception if interactivity is disabled' do
|
|||
|
ui = Inspec::UI.new(interactive: false)
|
|||
|
->() { ui.prompt }.must_raise Inspec::UserInteractionRequired
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
#=============================================================================#
|
|||
|
# Exit Codes
|
|||
|
#=============================================================================#
|
|||
|
# These are tested in functional tests
|
|||
|
describe 'Inspec UI Exit Codes' do
|
|||
|
[
|
|||
|
:EXIT_NORMAL,
|
|||
|
:EXIT_USAGE_ERROR,
|
|||
|
:EXIT_PLUGIN_ERROR,
|
|||
|
:EXIT_SKIPPED_TESTS,
|
|||
|
:EXIT_FAILED_TESTS,
|
|||
|
].each do |const_name|
|
|||
|
it "should define #{const_name}" do
|
|||
|
Inspec::UI.const_defined?(const_name).must_equal true
|
|||
|
end
|
|||
|
end
|
|||
|
end
|