Files
rdb/test/rdb_test.rb
T

797 lines
30 KiB
Ruby

# frozen_string_literal: true
require_relative 'test_helper'
require 'pty'
require_relative '../lib/rdb'
class SelectionTest < RdbCase
def test_splittable_pieces_and_errors
out, = run3('splittable', '-4', 'inventory')
assert_equal 4, data(File.read(File.join(@dir, 'inventoryaa'))).size
assert_equal 3, data(File.read(File.join(@dir, 'inventoryab'))).size
assert_includes File.read(File.join(@dir, 'inventoryab')), "Item\tAmount"
_o, err, st = run3('splittable', '-0', 'inventory')
refute st.success?
assert_match(/usage/, err)
write('big', "N\n-\n#{(1..700).to_a.join("\n")}\n")
_o, err, st = run3('splittable', '-1', 'big')
refute st.success?
assert_match(/only 676 names/, err)
assert_empty Dir[File.join(@dir, 'bigaa')] # nothing is written when it cannot all be written
end
def test_record_zero_and_one_are_the_first_and_negatives_count_from_the_end
{ '0' => '1', '1' => '1', '2' => '3', '3' => '6', '-1' => '6', '-2' => '3', '-3' => '1', '+2' => '3' }.each do |n, item|
assert_equal [[item]], data(rdb('record', n, file: 'owners')).map { |r| r.first(1) }, "record #{n}"
end
end
def test_record_beyond_either_end_is_an_empty_table
%w[4 9 -4 -9].each { |n| assert_equal [], data(rdb('record', n, file: 'owners')), "record #{n}" }
end
def test_record_rejects_junk
%w[abc 2.5 1e3].each do |n|
out, err, st = run3('record', n, stdin: File.read(File.join(@dir, 'owners')))
refute st.success?, "record #{n} should fail"
assert_equal '', out
assert_match(/not a record number/, err)
end
end
def test_row_numeric_condition
out = rdb('row', 'Cost > 5', file: 'inventory')
assert_equal %w[3 6 7], data(out).map(&:first)
end
def test_row_regex_and_or
out = rdb('row', 'Description ~ /burner/ || Amount < 10', file: 'inventory')
assert_equal %w[1 3 6 7], data(out).map(&:first)
end
def test_select_is_row
assert_equal rdb('row', 'Cost > 5', file: 'inventory'), rdb('select', 'Cost > 5', file: 'inventory')
end
def test_column_reorders_and_adds_empty
out = rdb('column', 'Cost', 'Nope', 'Item', file: 'inventory')
assert_equal %w[Cost Nope Item], out.lines.first.chomp.split("\t")
assert_equal ['5.00', '', '1'], data(out).first
end
def test_column_without_arguments_is_identity
assert_equal read('inventory'), rdb('column', file: 'inventory')
end
def test_compute_sprintf
out = rdb('compute', 'Value = sprintf("%5.2f", Cost * Amount)', file: 'inventory')
assert_equal '1312.75', data(out)[5][3].strip
end
def test_sorttable_numeric_reverse
out = rdb('sorttable', '-nr', 'Cost', file: 'inventory')
assert_equal 'scales', data(out).first.last
end
def test_sorttable_two_keys
out = rdb('sorttable', 'Account', 'Date', file: 'journal')
assert_equal %w[101 101 101], data(out).first(3).map { |r| r[1] }
end
def test_record
assert_equal ['3', '5', '8.00', '40.00', 'clamps'], data(rdb('record', '3', file: 'inventory')).first
end
def test_number
out = rdb('number', file: 'inventory')
assert_equal %w[number Item], out.lines.first.split("\t").first(2).map(&:chomp)
assert_equal '7', data(out).last.first
end
def test_rename_addcol_headoff_headon
assert_equal 'Qty', rdb('rename', 'Amount', 'Qty', file: 'inventory').lines.first.split("\t")[1]
assert_equal 'Extra', rdb('addcol', 'Extra', file: 'inventory').lines.first.chomp.split("\t").last
assert_equal 7, rdb('headoff', file: 'inventory').lines.size
assert_equal %w[a b], rdb('headon', 'a', 'b', stdin: "1\t2\n").lines.first.chomp.split("\t")
end
end
class SetsTest < RdbCase
def test_union_intersect_difference
assert_equal 6, data(rdb('union', 'carexpense', 'expense')).size
assert_equal [%w[820113 101.62 car v car\ repairs], ['820114', '81.80', 'insur', 'c', 'car insurance allstate']],
data(rdb('intersect', 'carexpense', 'expense'))
assert_equal ['meal'], data(rdb('difference', 'expense', 'carexpense')).map { |r| r[2] }
end
def test_union_with_stdin_dash
out = rdb('union', '-', 'carexpense', file: 'expense')
assert_equal '820107', data(out).first.first
end
def test_jointable_natural_and_outer
write('owners', "Item\tOwner\n----\t-----\n1\tann\n3\tbob\n9\tzed\n")
inner = data(rdb('jointable', 'inventory', 'owners'))
assert_equal %w[ann bob], inner.map(&:last)
left = data(rdb('jointable', '-a1', 'inventory', 'owners'))
assert_equal 7, left.size
right = data(rdb('jointable', '-a2', 'inventory', 'owners'))
assert_equal 'zed', right.last.last
end
def test_paste_and_split
out = rdb('paste.rdb', 'expense', 'carexpense')
assert_equal 10, out.lines.first.split("\t").size
rdb('splittable', '-4', 'inventory')
assert_equal 4, data(read('inventoryaa')).size
assert_equal 3, data(read('inventoryab')).size
end
end
class SummaryTest < RdbCase
def test_total_mean_max_min
assert_equal '324', data(rdb('total', 'Amount', file: 'inventory')).first[1]
assert_equal '175.00', data(rdb('maximum', 'Cost', file: 'inventory')).first[2]
assert_equal '0.50', data(rdb('minimum', 'Cost', file: 'inventory')).first[2]
assert_equal '46.29', data(rdb('mean', 'Amount', file: 'inventory')).first[1]
end
def test_datatype_width_precision_widest
assert_equal %w[0 0 2 2 -1], data(rdb('datatype', file: 'inventory')).first
assert_equal '14', data(rdb('width', 'Description', file: 'inventory')).first.last
assert_equal '2', data(rdb('precision', 'Cost', file: 'inventory')).first[2]
assert_equal %w[4 6 6 7 14], data(rdb('widest', file: 'inventory')).first
end
def test_total_l_prints_table_too
out = rdb('total', '-l', 'Amount', file: 'inventory')
assert_equal 11, out.lines.size
end
def test_subtotal
out = data(rdb('subtotal', 'Date', 'Debit', 'Credit', file: 'journal'))
assert_equal [%w[861222 25000 25000], %w[861223 10000 10000], %w[861224 30000 30000]],
out.map { |r| [r[0], r[2], r[3]] }
end
def test_schema_dbdict
out = data(rdb('schema', 'inventory'))
assert_equal %w[inventory 5 Description], out.last
assert_equal %w[5 Description], data(rdb('dbdict', file: 'inventory')).last
end
def test_fd
assert_equal "true\n", rdb('fd', 'Item', 'Cost', file: 'inventory')
out, _e, st = run3('fd', 'Amount', 'Cost', stdin: read('inventory'))
assert_equal "false\n", out
refute st.success?
end
end
class FormatTest < RdbCase
def test_justify_aligns_decimals
out = data(rdb('justify', file: 'inventory'))
assert_equal ' 14.75', out[5][2]
assert_equal '175.00', out[6][2]
assert_equal ' 1', out[0][0]
end
def test_compresss_pad_rmblank_blank
assert_equal data(read('inventory').then { |s| rdb('compresss', stdin: rdb('justify', stdin: s)) }),
data(read('inventory'))
assert_equal 'rubber gloves' + ' ' * 70, data(rdb('pad', file: 'inventory')).first.last
t = "A\tB\n-\t-\n1\t2\n \t \n3\t4\n"
assert_equal 2, data(rdb('rmblank', stdin: t)).size
assert_equal [' '], data(rdb('blank', stdin: "A\n-\nab\n")).flatten
end
def test_trim_squeezes_columns
out = rdb('trim', file: 'inventory').lines
assert_equal 'Item Amount Cost Value Description ', out.first.chomp
end
def test_case_commands
assert_equal "Rubber Gloves\n", rdb('cap', stdin: "rubber gloves\n")
assert_equal "BIG WORDS\n", rdb('uppercase', 'big', 'words')
assert_equal "big words\n", rdb('lowercase', 'BIG', 'WORDS')
assert_equal "here is the good stuff\n", rdb('trimblank', stdin: " here is the good stuff \n")
end
def test_padstring_length_ascii_chr
assert_equal " string\n", rdb('padstring', '10', 'string')
assert_equal "string \n", rdb('padstring', '-10', 'string')
assert_equal "11\n", rdb('length', 'word1 word2')
assert_equal "97 65 49 \n", rdb('ascii', 'aA1')
assert_equal "aA1\n", rdb('chr', '97', '65', '49')
end
def test_see_shows_tabs_and_ends
assert_equal "a^Ib$\n", rdb('see', stdin: "a\tb\n")
end
def test_insertdash
assert_equal "A\tBB\n-\t--\n1\t2\n", rdb('insertdash', stdin: "A\tBB\n1\t2\n")
end
def test_word
assert_equal "I\nlove\nyou\n", rdb('word', stdin: "I love you love\n")
end
end
class ConvertTest < RdbCase
def test_list_round_trip
assert_equal read('inventory'), rdb('listtotable', stdin: rdb('tabletolist', file: 'inventory'))
end
def test_commands_accept_lists
out = rdb('column', 'Name', 'City', file: 'maillist')
assert out.start_with?("\n"), 'a list in gives a list out'
assert_includes out, "City\tMemphis"
assert_includes rdb('row', 'Number == 2', file: 'maillist'), 'Chiquita'
end
def test_listtosh
out = rdb('listtosh', stdin: "\nA\tx y\nB\tit's\n")
assert_equal "A='x y'\nB='it'\\''s'\n", out
end
def test_tabletofix_and_back
fixed = rdb('tabletofix', 'Item=3r', 'Description=14', file: 'inventory')
assert_equal " 1rubber gloves \n", fixed.lines.first
t = rdb('fixtotable', 'Item=1,3', 'Description=4,14', stdin: fixed)
assert_equal ['1', 'rubber gloves'], data(t).first
end
def test_tabletostruct
out = rdb('tabletostruct', 'Table', 'table', stdin: "A\tB\tC\n-\t-\t-\n1\t2\t3\n")
assert_includes out, "struct Table\n{\nchar *A;"
assert_includes out, '{ "1","2","3" }'
end
def test_prolog_and_translation_formats
assert_equal "parent(Sally,Fred).\nparent(Mike,Fred).\nparent(Fred,Jane).\nfemale(Jane).\nfemale(Sally).\n",
rdb('tabletofact', 'parent', 'female')
assert_equal "mother(X,Y) :- female(X), parent(X,Y).\n", rdb('tabletorule', 'ruletable')
assert_equal "define(I,Ich)\ndefine(love,liebe)\ndefine(you,dich)\n", rdb('tabletom4', file: 'ed.t')
assert_equal "s/I/Ich/g\ns/love/liebe/g\ns/you/dich/g\n", rdb('tabletosed', file: 'ed.t')
end
def test_translate
write('ed', rdb('tabletom4', file: 'ed.t'))
assert_equal "Ich liebe dich.\n", rdb('translate', 'ed', stdin: "I love you.\n")
end
def test_tabletotbl
assert_includes rdb('tabletotbl', file: 'inventory'), ".TS\nn n n n l."
end
def test_dates
assert_equal "890515\n", rdb('computedate', '890331', '45')
assert_equal "890201\n", rdb('computedate', '890132', '0')
assert_equal '1752827', data(rdb('julian', 'Date', file: 'journal')).first.first
assert_equal '861222', data(rdb('gregorian', 'Date', stdin: rdb('julian', 'Date', file: 'journal'))).first.first
assert_equal '22/12/86', data(rdb('gregorian', '-e', 'Date', stdin: rdb('julian', 'Date', file: 'journal'))).first.first
assert_match(/\A\d{6}\n\z/, rdb('todaysdate'))
end
def test_soundex_and_like
assert_equal 'A352', data(rdb('soundex', stdin: "Name\n----\nAdams\n")).first[1]
assert_equal 'R163', data(rdb('soundex', stdin: "Name\n----\nRobert\n")).first[1]
write('name.x', rdb('soundex', file: 'name'))
assert_equal %w[Mann Moon], data(rdb('like', 'Mann', 'name')).flatten.uniq.sort
end
def test_hashkey
assert_equal "2\n", rdb('hashkey', '11', 'ABCD')
assert_equal "4\n", rdb('hashkey', '29', 'abcdefg')
end
end
class FastAccessTest < RdbCase
# Same rows come back whichever method finds them.
%w[b h i s].each do |m|
define_method("test_search_method_#{m}") do
rdb('index', "-m#{m}", 'inventory', 'Description') unless m == 's'
out = rdb('search', "-m#{m}", 'inventory', 'Description', stdin: "scales\nclamps\n")
assert_equal %w[scales clamps].sort, data(out).map(&:last).sort
end
end
def test_record_number_method
rdb('index', '-mr', 'inventory')
assert_equal 'clamps', data(rdb('search', '-mr', 'inventory', stdin: "3\n")).first.last
end
def test_partial_match
rdb('index', '-mi', '-x', 'inventory', 'Description')
out = rdb('search', '-mi', '-x', 'inventory', 'Description', stdin: "cl\n")
assert_equal %w[clamps cleaning\ cloth], data(out).map(&:last).sort
end
def test_keys_from_a_table_and_no_head
out = rdb('search', '-h', 'inventory', 'Description', stdin: "Description\n-----------\nscales\n")
assert_equal "7\t5\t175.00\t875.00\tscales\n", out
end
def test_seek_reports_offsets
out = rdb('seek', 'inventory', 'Item', stdin: "5\n")
from, to = out.split.map(&:to_i)
assert_equal "5\t99\t2.45\t242.55\tcleaning cloth\n", read('inventory').byteslice(from, to - from)
end
def test_delete_blanks_in_place_and_keeps_offsets
size = File.size(File.join(@dir, 'inventory'))
rdb('delete', 'inventory', 'Item', stdin: "4\n")
assert_equal size, File.size(File.join(@dir, 'inventory'))
assert_equal '', read('inventory').lines[5].delete("\t \n")
assert_equal 6, data(rdb('rmblank', file: 'inventory')).size
end
%w[r i h].each do |m|
define_method("test_delete_updates_index_#{m}") do
rdb('index', "-m#{m}", 'inventory', 'Item')
rdb('delete', "-m#{m}", 'inventory', 'Item', stdin: "2\n")
key = m == 'r' ? "2\n" : "2\n"
assert_equal '', rdb('search', "-m#{m}", '-h', 'inventory', 'Item', stdin: key) if m != 'r'
assert_equal 3, data(rdb('search', "-m#{m}", 'inventory', 'Item', stdin: "3\n")).first.first.to_i if m != 'r'
end
end
def test_append_updates_each_index
%w[i h r].each do |m|
rdb('index', "-m#{m}", 'inventory', 'Item')
rdb('append', '-h', "-m#{m}", 'inventory', 'Item', stdin: "#{8 + %w[i h r].index(m)}\t1\t1.00\t1.00\tnew #{m}\n")
end
assert_equal 'new i', data(rdb('search', '-mi', 'inventory', 'Item', stdin: "8\n")).first.last
assert_equal 'new h', data(rdb('search', '-mh', 'inventory', 'Item', stdin: "9\n")).first.last
assert_equal 'new r', data(rdb('search', '-mr', 'inventory', stdin: "10\n")).first.last
end
def test_append_with_head_line
rdb('append', 'inventory', stdin: "Item\tAmount\tCost\tValue\tDescription\n----\t------\t----\t-----\t-----------\n8\t1\t1\t1\tnew\n")
assert_equal 8, data(read('inventory')).size
end
def test_replace_in_place_pads_last_column
out = rdb('seek', 'inventory', 'Item', stdin: "3\n")
from, to = out.split.map(&:to_i)
size = File.size(File.join(@dir, 'inventory'))
rdb('replace', 'inventory', from.to_s, to.to_s, stdin: "3\t6\t8.00\t48.00\tclamp\n")
assert_equal size, File.size(File.join(@dir, 'inventory'))
assert_equal '6', data(rdb('search', 'inventory', 'Item', stdin: "3\n")).first[1]
end
def test_replace_too_big_moves_record_to_end_and_fixes_index
rdb('index', '-mi', 'inventory', 'Item')
from, to, xf, xt = rdb('seek', '-mi', 'inventory', 'Item', stdin: "3\n").split.map(&:to_i)
big = "3\t5\t8.00\t40.00\t#{'a very long description ' * 3}\n"
rdb('replace', '-mi', 'inventory', from.to_s, to.to_s, xf.to_s, xt.to_s, stdin: big)
hit = rdb('search', '-mi', 'inventory', 'Item', stdin: "3\n")
assert_includes hit, 'a very long description'
assert_equal 1, data(hit).size
assert_equal 'inventory', 'inventory' # the blanked old slot stays, so offsets of other rows are unchanged
end
def test_replace_with_a_no_append_flag_fails_when_too_big
from, to = rdb('seek', 'inventory', 'Item', stdin: "3\n").split.map(&:to_i)
_o, _e, st = run3('replace', '-a', 'inventory', from.to_s, to.to_s, stdin: "3\t5\t8\t40\t#{'x' * 80}\n")
refute st.success?
end
def test_lock_and_unlock
_o, _e, st = run3('lock', 'inventory', '111', '10', '20', '0', '0')
assert st.success?
_o, e, st = run3('lock', 'inventory', '222', '15', '25', '0', '0')
refute st.success?
assert_match(/locked by process 111/, e)
assert run3('lock', 'inventory', '222', '30', '40', '0', '0').last.success?
run3('unlock', 'inventory', '111', '10', '20')
run3('unlock', 'inventory', '222', '30', '40')
assert run3('lock', 'inventory', '333', '15', '25', '0', '0').last.success?
run3('unlock', 'inventory', '333', '15', '25')
end
def test_enter_appends_rows
out = rdb('enter', '-1', 'inventory', stdin: "8\n2\n3\n4\nnew thing\n")
assert_includes out, 'Item: '
assert_equal 'new thing', data(read('inventory')).last.last
end
end
class CheckingTest < RdbCase
def test_check_rdb_finds_short_rows
_o, e, st = run3('check.rdb', stdin: "A\tB\n-\t-\n1\t2\n3\n")
refute st.success?
assert_match(/Columns \(1\) do not equal headline columns \(2\)/, e)
assert run3('check.rdb', stdin: read('inventory')).last.success?
end
def test_domain
out = rdb('domain', 'colors', stdin: rdb('column', 'Colors', file: 'orders'))
assert_equal %w[farble yellow], data(out).flatten
assert_equal "x\n", rdb('domain', 'colors', 'black', 'x')
end
def test_validate_prints_message_before_bad_row
out = rdb('validate', 'Onhand < 0 {print "negative Onhand in line " NR}', file: 'badinventory')
assert_includes out, "negative Onhand in line 3\n3\t-5"
assert_equal 5, out.scan(/rubber|test tubes|clamps|plates|cleaning/).size
end
def test_searchtree
write('tree', "Parent\tChild\n------\t-----\nSally\tFred\nFred\tJane\n")
assert_equal "TRUE\n", rdb('searchtree', 'tree', 'Sally', 'Jane', 'Parent', 'Child')
_o, _e, st = run3('searchtree', 'tree', 'Jane', 'Sally', 'Parent', 'Child')
refute st.success?
end
def test_not
assert_equal 255, run3('not', 'true').last.exitstatus
assert run3('not', 'false').last.success?
end
end
class ReportTest < RdbCase
def test_report_fills_form
out = rdb('report', 'form', file: 'maillist')
assert_includes out, "Ronald McDonald\nMcDonalds\n123 Mac Attack\nMemphis, TENN 30000\nHi Ronald McDonald:"
assert_includes out, 'Hi Chiquita Banana:'
end
def test_report_runs_commands_and_leaves_unknown_names
write('f2', "<Name>|<!echo <City>!>|<Nope>\n")
assert_equal "Ronald McDonald|Memphis\n|<Nope>\nChiquita Banana|San Jose\n|<Nope>\n", rdb('report', 'f2', file: 'maillist')
end
def test_label
assert_equal "Ronald McDonald\nMcDonalds\n123 Mac Attack\nMemphis, TENN 30000\n", rdb('label', file: 'maillist').lines.first(4).join
end
def test_screen_generates_a_script
out = rdb('screen', stdin: "Name : <Name>\nCity : <City>\n")
assert_includes out, 'cursor 1 9 ; read Name;'
assert_includes out, 'cursor 2 9 ; read City;'
assert_includes out, 'echo "$Name' + "\t" + '$City"'
end
def test_reportwriter_pages
out = rdb('reportwriter', '-3', 'Prices', file: 'inventory')
assert_equal 3, out.scan('Page').size
assert_includes out, 'Report Date:'
end
end
class HelpTest < RdbCase
def test_whatis_whatwill_commands
assert_includes rdb('whatis', 'column'), " column - display columns of a table in any order\n column column [ Column ... ] < tableorlist\n"
assert_includes rdb('whatwill', 'total'), 'subtotal'
assert_operator data(rdb('commands')).size, :>, 120
assert_operator rdb('howmany').to_i, :>, 120
end
def test_rdb_lists_commands_and_man_renders
assert_includes rdb('rdb'), 'jointable'
page = rdb('rdb', 'man', 'addcol')
assert_includes page, 'Add a column to a table'
assert_includes page, 'EXAMPLE'
end
def test_termput_and_cursor
assert_equal "\e[H\e[2J", rdb('termput', 'cl')
assert_equal "\e[3;8H", rdb('cursor', '3', '8')
end
def test_testall_mentions_every_command
script = File.read(File.join(DEMO, 'testall.cmds'))
names = data(rdb('commands')).map(&:first)
missing = names.reject { |n| script.match?(/(^|[\s|;(])#{Regexp.escape(n)}(\s|$)/) }
assert_empty missing, "testall.cmds lacks an example for: #{missing.join(' ')}"
end
def test_testall_runs_demo_script
out = rdb('testall')
assert_includes out, '$ row'
end
end
class ActTest < RdbCase
def test_tax_foot_cashflow_explode_bom
assert_equal " 3369\n", rdb('tax', '20000', file: 'X83')
assert_equal %w[10001 2], data(rdb('explode', '10001', '2', 'parttable')).first
assert_equal '52', data(rdb('explode', '10001', '2', 'parttable')).last.last
assert_equal '1062', data(rdb('cashflow', file: 'cash'))[2][2]
end
def test_adjust_balance_close
write('trialbalance', "Account\tDebit\tCredit\tFormat\tTaxline\tShort\tName\n-------\t-----\t------\t------\t-------\t-----\t----\n1010\t11178\t\tB-A\t4L01\tcash\tCash\n3010\t\t13500\tI-I\t101a\tsales\tSales\n")
write('journaladjust', "Account\tDebit\tCredit\tFormat\tTaxline\tName\n-------\t-----\t------\t------\t-------\t----\n1010\t100\t\tB-A\t4L01\tCash\n")
rdb('adjust')
assert_equal %w[1010 11278], data(read('adjustedtrial')).first.first(2)
rdb('balance')
assert_includes read('balancesheet'), 'Total Assets'
rdb('close')
assert_includes read('journalnext'), 'brought forward'
end
def test_chartdup
write('chart', "Account\tShort\n-------\t-----\n1\ta\n1\tb\n2\ta\n")
_o, err, st = run3('chartdup')
out, = run3('chartdup')
assert_includes out, 'Duplicate Account numbers in chart'
assert_includes out, 'Duplicate Short names in chart'
assert st.success?, err
end
ICASE = { 'RDB_ICASE' => '1' }.freeze
def test_column_names_are_case_sensitive_by_default
_o, err, st = run3('column', 'cost', stdin: File.read(File.join(@dir, 'inventory')))
assert st.success?, err
assert_equal %w[cost], _o.lines.first.chomp.split("\t")
end
def test_icase_column_and_sorttable
out, err, st = run3('column', 'COST', 'item', stdin: File.read(File.join(@dir, 'inventory')), env: ICASE)
assert st.success?, err
assert_equal %w[Cost Item], out.lines.first.chomp.split("\t")
out, = run3('sorttable', '-nr', 'cost', stdin: File.read(File.join(@dir, 'inventory')), env: ICASE)
assert_equal 'scales', data(out).first.last
end
def test_icase_compute_and_awk_words
src = File.read(File.join(@dir, 'inventory'))
out, err, st = run3('compute', 'value = sprintf("%5.2f", cost * AMOUNT)', stdin: src, env: ICASE)
assert st.success?, err
assert_equal '1312.75', data(out)[5][3].strip
# a column called Length must not swallow the length() function
t = "Length\tName\n------\t----\n3\tab\n"
out, err, st = run3('compute', 'Length = length(name)', stdin: t, env: ICASE)
assert st.success?, err
assert_equal [%w[2 ab]], data(out)
end
def test_icase_ambiguous_column_is_an_error
t = "Name\tname\n----\t----\na\tb\n"
_o, err, st = run3('column', 'NAME', stdin: t, env: ICASE)
refute st.success?
assert_match(/ambiguous/, err)
out, = run3('column', 'name', stdin: t, env: ICASE)
assert_equal [%w[b]], data(out)
end
end
class MaskTest < RdbCase
def form(text, frame: false) = Rdb::Mask::Form.parse(text, frame: frame)
def type(session, *keys)
keys.each { |k| k.start_with?("\e") || k.size == 1 ? session.feed(k) : k.each_char { |c| session.feed(c) } }
end
def test_parse_fields_buttons_and_width
f = form("Name: <Name:10> < City >\n[ Save ] [Cancel]\n")
assert_equal %w[Name City], f.names
assert_equal [0, 6, 10], f.slots[0].to_a.first(3)
assert_equal 9, f.slots[1].width # the width of "< City >"
assert_equal %w[Save Cancel], f.buttons.map(&:label)
assert_equal ["Name: #{' ' * 10} #{' ' * 9}", '[ Save ] [Cancel]'], f.lines
end
def test_command_in_form_and_frame
f = form("<!echo hi!> <A>\n", frame: true)
assert_equal '│ hi │', f.lines[1]
assert_equal [1, 5], f.slots.first.to_a.first(2)
end
def test_typing_and_navigation
s = Rdb::Mask::Session.new(form("<A:5> <B:5> [ OK ] [ Cancel ]\n"))
type(s, 'one', "\t", 'two', "\x7f", "\r")
assert_equal({ 'A' => 'one', 'B' => 'tw' }, s.values)
assert_equal :button, s.kind
assert_equal :accept, s.feed("\r")
end
def test_cancel_by_button_and_escape
s = Rdb::Mask::Session.new(form("<A:5> [ Save ] [ Abbrechen ]\n"))
s.feed("\e[Z")
assert_equal :cancel, s.feed(' ')
assert_equal :cancel, Rdb::Mask::Session.new(form("<A>\n")).feed("\e")
end
def test_enter_on_last_field_accepts_without_buttons
s = Rdb::Mask::Session.new(form("<A:3> <B:3>\n"))
assert_nil s.feed("\r")
assert_equal :accept, s.feed("\r")
end
def test_same_column_twice_is_one_field
s = Rdb::Mask::Session.new(form("<A:3> <A:3>\n"))
type(s, 'xy')
assert_equal({ 'A' => 'xy' }, s.values)
assert_equal %w[A], form("<A:3> <A:3>\n").names
end
def test_show_prints_picture_without_terminal
write('f', "Name: <Name:6>\n[ Save ] [ Cancel ]\n")
out = rdb('mask', '-s', file: 'f')
assert_includes out, "Name: ______\n[ Save ] [ Cancel ]"
assert_includes out, " Name\trow 1 col 7 width 6"
assert_includes out, " [Cancel]\trow 2 col 10 (cancels)"
assert_equal out, rdb('mask', '-s', 'f') # a file name works too
end
# Drive mask on a pseudo terminal; its table goes to a file, like in a pipe.
def run_on_pty(*keys)
out = File.join(@dir, 'mask.out')
result = nil
PTY.spawn({ 'PATH' => "#{BIN}:#{ENV['PATH']}" }, 'sh', '-c', "cd #{@dir} && mask < f > mask.out") do |r, w, pid|
sleep 0.5
keys.each do |k|
w.write k
sleep 0.1
r.read_nonblock(65_536) rescue nil
end
sleep 0.3
r.read_nonblock(65_536) rescue nil
_, st = Process.wait2(pid)
result = [File.exist?(out) ? File.read(out) : '', st.exitstatus]
end
result
end
def test_mask_on_a_terminal_writes_a_table
write('f', "Name: <Name:10>\nCity: <City:10>\n[ Save ] [ Cancel ]\n")
table, status = run_on_pty("Ann\r", "Rome\r", "\r")
assert_equal 0, status
assert_equal "Name\tCity\n----\t----\nAnn\tRome\n", table
end
def test_mask_cancel_writes_nothing
write('f', "Name: <Name:10>\n[ Save ] [ Cancel ]\n")
table, status = run_on_pty("Ann", "\e[Z", "\r")
assert_equal 1, status
assert_equal '', table
end
end
class PickTest < RdbCase
def session(multi: true, height: 3)
t = Rdb::Table.read(File.open(File.join(DEMO, 'inventory')))
Rdb::Pick::Session.new(t.head, t.rows, multi: multi).tap { |x| x.resize(height, 80) }
end
def keys(sess, words) = Rdb::Pick.script(words).map { |k| sess.feed(k) }.compact.last
def test_mark_and_select
s = session
assert_equal :accept, keys(s, 'down space down down space enter')
assert_equal %w[2 5], s.selected.map(&:first)
end
def test_single_mode_takes_the_cursor_row_and_ignores_space
s = session(multi: false)
assert_equal :accept, keys(s, 'down down space enter')
assert_equal %w[3], s.selected.map(&:first)
end
def test_scrolling_keeps_the_cursor_in_view
s = session(height: 3)
keys(s, 'down down down down')
assert_equal [4, 2], [s.cursor, s.top]
keys(s, 'end')
assert_equal [6, 4], [s.cursor, s.top]
keys(s, 'pgup')
assert_equal 3, s.cursor
keys(s, 'home')
assert_equal [0, 0], [s.cursor, s.top]
end
def test_filter_and_clear
s = session
keys(s, '/clam')
assert_predicate s, :typing?
keys(s, 'enter')
assert_equal [2], s.visible
keys(s, '/ esc')
assert_equal 7, s.visible.size
end
def test_marks_survive_a_filter_and_a_all_toggles
s = session
keys(s, 'space /scales enter')
assert_equal [0], s.marked
keys(s, 'a')
assert_equal [0, 6], s.marked.sort
keys(s, 'a')
assert_equal [0], s.marked
end
def test_cancel_and_empty_choice
assert_equal :cancel, keys(session, 'esc')
assert_equal :cancel, keys(session, '/zzz enter enter')
end
def test_command_with_keys_script_is_a_filter
out = rdb('pick', '-m', '-k', 'down space down down space enter', file: 'inventory')
assert_equal %w[2 5], data(out).map(&:first)
assert_equal '292.55', data(rdb('total', 'Value', stdin: out)).first[3]
end
def test_command_cancelled_exits_1_with_no_output
out, _err, st = run3('pick', '-k', 'esc', stdin: File.read(File.join(@dir, 'inventory')))
refute st.success?
assert_equal '', out
end
def test_command_reads_a_list
out = rdb('pick', '-k', 'down enter', stdin: rdb('tabletolist', file: 'owners'))
assert_equal [%w[3 bob]], data(out)
end
def run_on_pty(*key_strokes)
out = File.join(@dir, 'pick.out')
result = nil
PTY.spawn({ 'PATH' => "#{BIN}:#{ENV['PATH']}" }, 'sh', '-c', "cd #{@dir} && pick -m < inventory > pick.out") do |r, w, pid|
sleep 0.5
key_strokes.each do |k|
w.write k
sleep 0.1
r.read_nonblock(65_536) rescue nil
end
sleep 0.3
r.read_nonblock(65_536) rescue nil
_, st = Process.wait2(pid)
result = [File.exist?(out) ? File.read(out) : '', st.exitstatus]
end
result
end
def test_pick_on_a_terminal
table, status = run_on_pty("\e[B", ' ', "\e[B", ' ', "\r")
assert_equal 0, status
assert_equal %w[2 4], data(table).map(&:first)
end
def test_pick_on_a_terminal_cancel
table, status = run_on_pty("\e[B", "\e")
assert_equal 1, status
assert_equal '', table
end
end
class TaxTablesTest < RdbCase
def tax(income, table) = rdb('tax', income.to_s, file: table).to_i
def test_at_2026
assert_equal 0, tax(13_539, 'taxAT2026')
assert_equal 11_447, tax(50_000, 'taxAT2026') # 6 030.40 + 13 542 * 40 %
assert_equal 33_818, tax(100_000, 'taxAT2026')
end
def test_ch_federal_2026
assert_equal 0, tax(15_200, 'taxCH2026')
assert_equal 401, tax(50_000, 'taxCH2026')
assert_equal 2684, tax(100_000, 'taxCH2026')
assert_equal 91_310, tax(794_000, 'taxCH2026')
end
def test_li_landessteuer_2026
assert_equal 996, tax(50_000, 'taxLI2026') # 0,04 * x - 1 004
assert_equal 3256, tax(100_000, 'taxLI2026') # 0,05 * x - 1 744
assert_equal 10_464, tax(211_400, 'taxLI2026')
assert_equal 0, tax(15_855, 'taxLI2026')
end
# § 32a EStG: the table is a straight-line approximation of the curved zones
def test_de_2026_follows_the_formula
[12_348, 14_000, 17_799, 20_000, 33_333, 50_000, 69_878, 100_000, 277_825, 300_000].each do |x|
exact = if x <= 12_348 then 0
elsif x <= 17_799 then y = (x - 12_348) / 10_000.0; ((914.51 * y) + 1400) * y
elsif x <= 69_878 then z = (x - 17_799) / 10_000.0; (((173.10 * z) + 2397) * z) + 1034.87
elsif x <= 277_825 then (0.42 * x) - 11_135.63
else (0.45 * x) - 19_470.38
end
assert_in_delta exact.floor, tax(x, 'taxDE2026'), 1, "income #{x}"
end
end
end