require 'test/unit'
require 's4t-utils'
require 'builder'
require 'user-choices'
include S4tUtils
set_test_paths(__FILE__)
require 'tempfile'
# The general contract of these objects.
class TestAbstractSource < Test::Unit::TestCase
include UserChoices
class SubHash < UserChoices::AbstractSource
# New never takes arguments. Class-specific initialization is done
# with an appropriately-named method.
#
# That method must return self and set up the external_name hash
# so that all symbols handled by this object can be given an external
# name.
def only_symbol(symbol, external_name, value)
@external_names[symbol] = external_name
@symbol = symbol
@value = value
self
end
# After fill(), values have been read, but not checked.
def fill; self[@symbol] = @value; end
def source; "the test hash"; end
end
def test_specific_initializer_notes_external_names
sh = SubHash.new.only_symbol(:sym, "name", "val")
assert_equal('name', sh.external_names[:sym])
end
def test_filling_sets_values
sh = SubHash.new.only_symbol(:sym, "name", "val")
sh.fill
assert_equal('val', sh[:sym])
end
def test_will_do_conversion_when_told
sh = SubHash.new.only_symbol(:sym, "name", "1")
sh.fill
conversions = { :sym => [Conversion.for(:integer)] }
sh.apply(conversions)
assert_equal(1, sh[:sym])
end
# Checking - really just want to know that the error message comes out right.
def test_will_do_integer_error_checking_when_told
sh = SubHash.new.only_symbol(:sym, "name", "val")
sh.fill
conversions = { :sym => [Conversion.for(:integer)] }
assert_raises_with_matching_message(StandardError,
/^Error in the test hash: name's value must be an integer, and 'val' doesn't look right/) {
sh.apply(conversions)
}
end
def test_will_do_boolean_error_checking_when_told
sh = SubHash.new.only_symbol(:sym, "name", "val")
sh.fill
conversions = { :sym => [Conversion.for(:boolean)] }
assert_raises_with_matching_message(StandardError,
/^Error in the test hash: name's value must be a boolean, and 'val' doesn't look right/) {
sh.apply(conversions)
}
end
def test_will_do_alternative_error_checking_when_told
sh = SubHash.new.only_symbol(:sym, "name", "val")
sh.fill
conversions = { :sym => [Conversion.for(["foo", "bar"])] }
assert_raises_with_matching_message(StandardError,
/^Error in the test hash: name's value must be one of 'foo' or 'bar', and 'val' doesn't look right/) {
sh.apply(conversions)
}
end
def test_will_do_exact_length_checking_when_told
sh = SubHash.new.only_symbol(:sym, "name", ["one", "two"])
sh.fill
conversions = { :sym => [Conversion.for([:string]), # actually not needed.
Conversion.for(:length => 5)] }
assert_raises_with_matching_message(StandardError,
/^Error in the test hash: name's value must be of length 5, and \["one", "two"\] doesn't look right/) {
sh.apply(conversions)
}
end
def test_will_do_range_length_checking_when_told
sh = SubHash.new.only_symbol(:sym, "name", ["one", "two"])
sh.fill
conversions = { :sym => [Conversion.for([:string]), # actually not needed.
Conversion.for(:length => 3..5)] }
assert_raises_with_matching_message(StandardError,
/^Error in the test hash: name's value must be a list whose length is in this range: 3..5, and \["one", "two"\] doesn't look right/) {
sh.apply(conversions)
}
end
def test_it_is_ok_for_key_not_to_appear
sh = SubHash.new.only_symbol(:sym, "name", "val")
sh.fill
conversions = { :another => [Conversion.for(:integer)] }
sh.apply(conversions)
assert_equal('val', sh[:sym])
assert_equal(nil, sh[:another])
end
end
class DefaultSourceTest < Test::Unit::TestCase
include UserChoices
def setup
@choices = DefaultSource.new.use_hash(:a => 'a')
@choices.fill
end
def test_default_values_are_created_with_key_not_string
assert_equal(1, @choices.size)
assert_equal('a', @choices[:a])
assert_equal(':a', @choices.external_names[:a])
end
def test_nil_is_default_default
assert_nil(@choices[:foo])
end
def test_error_message_will_look_good
assert_raises_with_matching_message(StandardError,
/^Error in the default values: :a's value/) {
@choices.apply( :a => [Conversion.for(:integer)])
}
end
def test_value_conversions_are_from_strings
c = DefaultSource.new.use_hash(:a => '5')
c.fill
c.apply(:a => [Conversion.for(:integer)])
assert_equal(5, c[:a])
end
end
class EnvironmentSourceTest < Test::Unit::TestCase
include UserChoices
def test_the_environment_args_of_interest_can_be_described_by_prefix
with_environment_vars('amazon_option' => "1") do
choices = EnvironmentSource.new.with_prefix('amazon_')
choices.fill
assert_true(choices.has_key?(:option))
assert_equal('1', choices[:option])
end
end
def test_the_environment_args_can_use_empty_string_as_the_prefix
# Though it's a silly thing to do.
with_environment_vars('amazon_option' => "1") do
choices = EnvironmentSource.new.with_prefix('')
choices.fill
assert_true(choices.has_key?(:amazon_option))
assert_equal('1', choices[:amazon_option])
end
end
def test_the_environment_args_of_interest_can_be_listed_explicitly
with_environment_vars('amazon_option' => "1",
'root' => 'ok',
'~' => 'ok, too') do
choices = EnvironmentSource.new.mapping(:option => 'amazon_option',
:root => 'root',
:home => '~')
choices.fill
assert_equal(3, choices.size)
assert_equal('1', choices[:option])
assert_equal('ok', choices[:root])
assert_equal('ok, too', choices[:home])
end
end
def test_can_also_combine_both_forms
with_environment_vars('amazon_o' => "1",
'other_option' => 'still found') do
choices = EnvironmentSource.new.with_prefix('amazon_').mapping(:other => 'other_option')
choices.fill
assert_equal(2, choices.size)
assert_equal('1', choices[:o])
assert_equal('still found', choices[:other])
end
end
def test_the_order_of_combination_does_not_matter
with_environment_vars('amazon_o' => "1",
'other_option' => 'still found') do
choices = EnvironmentSource.new.mapping(:other => 'other_option').with_prefix('amazon_')
choices.fill
assert_equal(2, choices.size)
assert_equal('1', choices[:o])
assert_equal('still found', choices[:other])
end
end
def test_unmentioned_environment_vars_are_ignored
with_environment_vars('unfound' => "1") do
choices = EnvironmentSource.new.with_prefix("my_")
choices.fill
assert_true(choices.empty?)
end
end
def test_nil_is_default
with_environment_vars('found' => "1") do
choices = EnvironmentSource.new.mapping(:option => 'f')
choices.fill
assert_nil(choices[:foo])
assert_nil(choices[:option]) # for fun
end
end
def test_value_checking_is_set_up_properly
with_environment_vars('amazon_option' => "1") do
assert_raises_with_matching_message(StandardError,
/^Error in the environment: amazon_option's value/) {
choices = EnvironmentSource.new.with_prefix('amazon_')
choices.fill
choices.apply(:option => [Conversion.for(:boolean)])
}
end
end
def test_value_conversion_is_set_up_properly
with_environment_vars('a' => "1", 'names' => 'foo,bar') do
choices = EnvironmentSource.new.mapping(:a => 'a', :names => 'names')
choices.fill
choices.apply(:a => [Conversion.for(:integer)],
:names => [Conversion.for([:string])])
assert_equal(1, choices[:a])
assert_equal(['foo', 'bar'], choices[:names])
end
end
end
# Common behavior for all config files. Using XML as an example.
class FileSourceTestCase < Test::Unit::TestCase
include UserChoices
def setup
builder = Builder::XmlMarkup.new(:indent => 2)
@some_xml = builder.config {
builder.reverse("true")
builder.maximum("53")
builder.host('a.com')
builder.host('b.com')
}
end
def test_config_file_need_not_exist
assert_false(File.exist?(".amazonrc"))
choices = XmlConfigFileSource.new.from_file(".amazonrc")
assert_true(choices.empty?)
end
def test_config_file_value_checking_is_set_up_properly
with_local_config_file(".amazonrc", @some_xml) do
assert_raises_with_matching_message(StandardError,
%r{Error in configuration file ./.amazonrc: maximum's value.*'low'.*'high'}) {
choices = XmlConfigFileSource.new.from_file(".amazonrc")
choices.fill
choices.apply(:maximum => [Conversion.for(['low', 'high'])])
}
end
end
def test_value_conversions_are_set_up_properly
with_local_config_file('.amazonrc', @some_xml) do
choices = XmlConfigFileSource.new.from_file('.amazonrc')
choices.fill
choices.apply(:maximum => [Conversion.for(:integer)])
assert_equal(53, choices[:maximum])
end
end
def test_complete_paths_to_config_file_are_allowed
tempfile = Tempfile.new('path-test')
tempfile.puts(@some_xml)
tempfile.close
choices = XmlConfigFileSource.new.from_complete_path(tempfile.path)
choices.fill
assert_equal('53', choices[:maximum])
end
def test_unmentioned_values_are_nil
with_local_config_file('.amazonrc', @some_xml) do
choices = XmlConfigFileSource.new.from_file('.amazonrc')
choices.fill
assert_nil(choices[:unmentioned])
end
end
def test_dashed_choice_names_are_underscored
with_local_config_file('.amazonrc', "5") do
choices = XmlConfigFileSource.new.from_file('.amazonrc')
choices.fill
assert_equal('5', choices[:the_name])
end
end
end
class XmlConfigFileSourceTestCase < Test::Unit::TestCase
include UserChoices
def setup
builder = Builder::XmlMarkup.new(:indent => 2)
@some_xml = builder.config {
builder.reverse("true")
builder.maximum("53")
builder.host('a.com')
builder.host('b.com')
}
end
def test_xml_config_file_normal_use
with_local_config_file('.amazonrc', @some_xml) {
choices = XmlConfigFileSource.new.from_file(".amazonrc")
choices.fill
choices.apply(:reverse => [Conversion.for(:boolean)],
:maximum => [Conversion.for(:integer)])
assert_equal(3, choices.size)
assert_equal(true, choices[:reverse])
assert_equal(53, choices[:maximum])
assert_equal(['a.com', 'b.com'], choices[:host])
}
end
def test_config_file_with_bad_xml
with_local_config_file('.amazonrc',"") {
assert_raise_with_matching_message(REXML::ParseException,
%r{Badly formatted configuration file ./.amazonrc: .*Missing end tag}) do
XmlConfigFileSource.new.from_file(".amazonrc")
end
}
end
end
class YamlConfigFileSourceTestCase < Test::Unit::TestCase
include UserChoices
def setup
@some_yaml = "
| ---
| reverse: true
| maximum: 53
| host:
| - a.com
| - b.com
| list-arg: 1,2, 3
".without_pretty_indentation('|')
end
def test_string_assurance
choices = YamlConfigFileSource.new
a = [1]
choices.ensure_element_is_string(a, 0)
assert_equal(["1"], a)
h = {'foo' => false }
choices.ensure_element_is_string(h, 'foo')
assert_equal({'foo' => 'false'}, h)
a = [1, 2.0, true, 'already']
choices.ensure_array_values_are_strings(a)
assert_equal(['1', '2.0', 'true', 'already'], a)
h = {'1' => '2', 'false' => true, 99 => 100 }
choices.ensure_hash_values_are_strings(h)
assert_equal({'1' => '2', 'false' => 'true', 99 => '100' }, h)
h = {'1' => '2', 'false' => [1, true], 99 => {100 => true}}
choices.ensure_hash_values_are_strings(h)
assert_equal({'1' => '2', 'false' => ['1', 'true'], 99 => {100 => 'true'} }, h)
end
def test_yaml_config_file_normal_use
with_local_config_file('.amazonrc', @some_yaml) {
choices = YamlConfigFileSource.new.from_file(".amazonrc")
choices.fill
assert_equal(4, choices.size)
assert_equal("true", choices[:reverse])
assert_equal("53", choices[:maximum])
assert_equal(['a.com', 'b.com'], choices[:host])
assert_equal("1,2, 3", choices[:list_arg])
}
end
def test_config_file_with_bad_yaml
with_local_config_file('.amazonrc',"foo:\n\tfred") {
assert_raise_with_matching_message(ArgumentError,
%r{Badly formatted configuration file ./.amazonrc: .*syntax error}) do
pp YamlConfigFileSource.new.from_file(".amazonrc"), 'should never have been reached'
end
}
end
end