dtas 0.11.0 → 0.12.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +4 -0
  3. data/Documentation/GNUmakefile +1 -1
  4. data/Documentation/dtas-console.txt +1 -0
  5. data/Documentation/dtas-player_protocol.txt +19 -5
  6. data/Documentation/dtas-splitfx.txt +13 -0
  7. data/Documentation/dtas-tl.txt +16 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +2 -2
  10. data/INSTALL +3 -3
  11. data/README +4 -0
  12. data/bin/dtas-archive +5 -1
  13. data/bin/dtas-console +13 -6
  14. data/bin/dtas-cueedit +1 -1
  15. data/bin/dtas-mlib +47 -0
  16. data/bin/dtas-readahead +211 -0
  17. data/bin/dtas-sinkedit +1 -1
  18. data/bin/dtas-sourceedit +1 -1
  19. data/bin/dtas-splitfx +15 -6
  20. data/bin/dtas-tl +81 -5
  21. data/dtas.gemspec +2 -2
  22. data/lib/dtas.rb +17 -0
  23. data/lib/dtas/buffer/read_write.rb +21 -19
  24. data/lib/dtas/buffer/splice.rb +1 -2
  25. data/lib/dtas/format.rb +2 -2
  26. data/lib/dtas/mlib.rb +500 -0
  27. data/lib/dtas/mlib/migrations/0001_initial.rb +42 -0
  28. data/lib/dtas/nonblock.rb +24 -0
  29. data/lib/dtas/parse_freq.rb +29 -0
  30. data/lib/dtas/parse_time.rb +5 -2
  31. data/lib/dtas/pipe.rb +2 -1
  32. data/lib/dtas/player.rb +21 -41
  33. data/lib/dtas/player/client_handler.rb +175 -92
  34. data/lib/dtas/process.rb +41 -17
  35. data/lib/dtas/sigevent/pipe.rb +6 -5
  36. data/lib/dtas/sink.rb +1 -1
  37. data/lib/dtas/source/splitfx.rb +14 -0
  38. data/lib/dtas/splitfx.rb +52 -36
  39. data/lib/dtas/track.rb +13 -0
  40. data/lib/dtas/tracklist.rb +148 -43
  41. data/lib/dtas/unix_accepted.rb +49 -32
  42. data/lib/dtas/unix_client.rb +1 -1
  43. data/lib/dtas/unix_server.rb +17 -9
  44. data/lib/dtas/watchable.rb +16 -5
  45. data/test/test_env.rb +16 -0
  46. data/test/test_mlib.rb +31 -0
  47. data/test/test_parse_freq.rb +18 -0
  48. data/test/test_player_client_handler.rb +12 -12
  49. data/test/test_splitfx.rb +0 -29
  50. data/test/test_tracklist.rb +75 -17
  51. data/test/test_unixserver.rb +0 -11
  52. metadata +16 -4
@@ -17,55 +17,48 @@ def initialize(sock)
17
17
  def emit(msg)
18
18
  buffered = @send_buf.size
19
19
  if buffered == 0
20
- begin
21
- @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR)
22
- return :wait_readable
23
- rescue Errno::EAGAIN
20
+ case rv = sendmsg_nonblock(msg)
21
+ when :wait_writable
24
22
  @send_buf << msg
25
- return :wait_writable
26
- rescue => e
27
- return e
23
+ rv
24
+ else
25
+ :wait_readable
28
26
  end
29
- elsif buffered > 100
30
- return RuntimeError.new("too many messages buffered")
31
27
  else # buffered > 0
32
28
  @send_buf << msg
33
- return :wait_writable
29
+ :wait_writable
34
30
  end
31
+ rescue => e
32
+ e
35
33
  end
36
34
 
37
35
  # flushes pending data if it got buffered
38
36
  def writable_iter
39
- begin
40
- msg = @send_buf.shift or return :wait_readable
41
- @to_io.send_nonblock(msg, Socket::MSG_EOR)
42
- rescue Errno::EAGAIN
43
- @send_buf.unshift(msg)
44
- return :wait_writable
45
- rescue => e
46
- return e
47
- end while true
37
+ case sendmsg_nonblock(@send_buf[0])
38
+ when :wait_writable then return :wait_writable
39
+ else
40
+ @send_buf.shift
41
+ @send_buf.empty? ? :wait_readable : :wait_writable
42
+ end
43
+ rescue => e
44
+ e
48
45
  end
49
46
 
50
47
  def readable_iter
51
- io = @to_io
52
- nread = io.nread
48
+ nread = @to_io.nread
53
49
 
54
50
  # EOF, assume no spurious wakeups for SOCK_SEQPACKET
55
51
  return nil if nread == 0
56
52
 
57
- begin
58
- begin
59
- msg, _, _ = io.recvmsg_nonblock(nread, 0, 0)
60
- rescue Errno::EAGAIN
61
- return :wait_readable
62
- rescue EOFError, SystemCallError
63
- return nil
64
- end
53
+ case msg = recv_nonblock(nread)
54
+ when :wait_readable then return msg
55
+ when '', nil then return nil # EOF
56
+ else
65
57
  yield(self, msg) # DTAS::Player deals with this
66
- nread = io.nread
67
- end while nread > 0
68
- :wait_readable
58
+ end
59
+ @send_buf.empty? ? :wait_readable : :wait_writable
60
+ rescue SystemCallError
61
+ nil
69
62
  end
70
63
 
71
64
  def close
@@ -75,4 +68,28 @@ def close
75
68
  def closed?
76
69
  @to_io.closed?
77
70
  end
71
+
72
+ if RUBY_VERSION.to_f >= 2.3
73
+ def sendmsg_nonblock(msg)
74
+ @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR, exception: false)
75
+ end
76
+
77
+ def recv_nonblock(len)
78
+ @to_io.recv_nonblock(len, exception: false)
79
+ end
80
+ else
81
+ def sendmsg_nonblock(msg)
82
+ @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR)
83
+ rescue IO::WaitWritable
84
+ :wait_writable
85
+ end
86
+
87
+ def recv_nonblock(len)
88
+ @to_io.recv_nonblock(len)
89
+ rescue IO::WaitReadable
90
+ :wait_readable
91
+ rescue EOFError
92
+ nil
93
+ end
94
+ end
78
95
  end
@@ -41,6 +41,6 @@ def res_wait(timeout = nil)
41
41
  IO.select([@to_io], nil, nil, timeout)
42
42
  nr = @to_io.nread
43
43
  nr > 0 or raise EOFError, "unexpected EOF from server"
44
- @to_io.recvmsg(nr, 0, 0)[0]
44
+ @to_io.recv(nr)
45
45
  end
46
46
  end
@@ -58,12 +58,10 @@ def write_failed(client, e)
58
58
 
59
59
  def readable_iter
60
60
  # we do not do anything with the block passed to us
61
- begin
62
- sock, _ = @to_io.accept_nonblock
63
- @readers[DTAS::UNIXAccepted.new(sock)] = true
64
- rescue Errno::ECONNABORTED # ignore this, it happens
65
- rescue Errno::EAGAIN
66
- return :wait_readable
61
+ case rv = accept_nonblock
62
+ when :wait_readable then return rv
63
+ else
64
+ @readers[DTAS::UNIXAccepted.new(rv[0])] = true
67
65
  end while true
68
66
  end
69
67
 
@@ -88,9 +86,7 @@ def wait_ctl(io, err)
88
86
  # - a consumer (e.g. DTAS::Sink) just became writable, but the
89
87
  # corresponding DTAS::Buffer was already readable in a previous
90
88
  # call.
91
- when nil
92
- io.close
93
- when StandardError
89
+ when nil, StandardError
94
90
  io.close
95
91
  else
96
92
  raise "BUG: wait_ctl invalid: #{io} #{err.inspect}"
@@ -117,4 +113,16 @@ def run_once
117
113
  wait_ctl(io, io.readable_iter { |_io, msg| yield(_io, msg) })
118
114
  end
119
115
  end
116
+
117
+ if RUBY_VERSION.to_f >= 2.3
118
+ def accept_nonblock
119
+ @to_io.accept_nonblock(exception: false)
120
+ end
121
+ else
122
+ def accept_nonblock
123
+ @to_io.accept_nonblock
124
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO
125
+ :wait_readable
126
+ end
127
+ end
120
128
  end
@@ -18,7 +18,8 @@ def self.new
18
18
  def readable_iter
19
19
  or_call = false
20
20
  while event = take(true) # drain the buffer
21
- if (event.mask & FLAGS) != 0 && @watching[1] == event.name
21
+ w = @watches[event.wd] or next
22
+ if (event.mask & FLAGS) != 0 && w[event.name]
22
23
  or_call = true
23
24
  end
24
25
  end
@@ -31,19 +32,29 @@ def readable_iter
31
32
  end
32
33
 
33
34
  # we must watch the directory, since
34
- def watch_file(path, blk)
35
+ def watch_files(paths, blk)
36
+ @watches = {} # wd -> { basename -> true }
35
37
  @on_readable = blk
36
- @watching = File.split(File.expand_path(path))
37
- add_watch(@watching[0], FLAGS)
38
+ @dir2wd = {}
39
+ Array(paths).each do |path|
40
+ watchdir, watchbase = File.split(File.expand_path(path))
41
+ wd = @dir2wd[watchdir] ||= add_watch(watchdir, FLAGS)
42
+ m = @watches[wd] ||= {}
43
+ m[watchbase] = true
44
+ end
38
45
  end
39
46
  end
40
47
 
41
48
  def watch_begin(blk)
42
49
  @ino = InotifyReadableIter.new
43
- @ino.watch_file(@infile, blk)
50
+ @ino.watch_files(@watch_extra << @infile, blk)
44
51
  @ino
45
52
  end
46
53
 
54
+ def watch_extra(paths)
55
+ @ino.watch_extra(paths)
56
+ end
57
+
47
58
  # Closing the inotify descriptor (instead of using inotify_rm_watch)
48
59
  # is cleaner because it avoids EINVAL on race conditions in case
49
60
  # a directory is deleted: https://lkml.org/lkml/2007/7/9/3
@@ -52,4 +52,20 @@ def test_expand
52
52
  res = env_expand({"PATH"=>"$PATH"}, expand: true)
53
53
  assert_equal ENV["PATH"], res["PATH"]
54
54
  end
55
+
56
+ def test_ary
57
+ ENV['HELLO'] = 'HIHI'
58
+ ENV['PAATH'] = '/usr/local/bin:/usr/bin:/bin'
59
+ env = { 'BLAH' => [ '$HELLO/WORLD', '$PAATH', '$(echo hello world)' ] }
60
+ res = env_expand(env, expand: true)
61
+ exp = [ "HIHI/WORLD", ENV['PAATH'], 'hello world' ]
62
+ assert_equal exp, Shellwords.split(res['BLAH'])
63
+ env = {
64
+ 'BLAH' => [ '$(echo hello world)' ],
65
+ 'MOAR' => [ '$BLAH', 'OMG HALP SPACES' ]
66
+ }
67
+ res = env_expand(env, expand: true)
68
+ exp = ["hello\\ world", "OMG HALP SPACES"]
69
+ assert_equal exp, Shellwords.split(res['MOAR'])
70
+ end
55
71
  end
@@ -0,0 +1,31 @@
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'helper'
4
+ begin
5
+ require 'dtas/mlib'
6
+ require 'sequel/no_core_ext'
7
+ require 'sqlite3'
8
+ rescue LoadError => err
9
+ warn "skipping mlib test: #{err.message}"
10
+ exit 0
11
+ end
12
+
13
+ class TestMlib < Testcase
14
+ def setup
15
+ @db = Sequel.sqlite(':memory:')
16
+ end
17
+
18
+ def test_migrate
19
+ ml = DTAS::Mlib.new(@db)
20
+ begin
21
+ $-w = false
22
+ ml.migrate
23
+ tables = @db.tables
24
+ ensure
25
+ $-w = true
26
+ end
27
+ [ :nodes, :tags, :vals, :comments ].each do |t|
28
+ assert tables.include?(t), "missing #{t}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require './test/helper'
4
+
5
+ require 'dtas/parse_freq'
6
+
7
+ class TestParseFreq < Testcase
8
+ include DTAS::ParseFreq
9
+
10
+ def test_parse_freq
11
+ assert_equal(4000, parse_freq('4k'))
12
+ assert_equal(-4000, parse_freq('-4k'))
13
+ assert_equal(-4900, parse_freq('-4.9k'))
14
+ assert_equal(-4900, parse_freq('-4.9k', :int))
15
+
16
+ assert_equal(4900.5, parse_freq('4.9005k', :float))
17
+ end
18
+ end
@@ -19,23 +19,23 @@ def setup
19
19
  def test_delete
20
20
  @sinks["default"] = DTAS::Sink.new
21
21
  @targets = []
22
- sink_handler(@io, %w(rm default))
22
+ dpc_sink(@io, %w(rm default))
23
23
  assert @sinks.empty?
24
24
  assert_equal %w(OK), @io.to_a
25
25
  end
26
26
 
27
27
  def test_delete_noexist
28
- sink_handler(@io, %w(rm default))
28
+ dpc_sink(@io, %w(rm default))
29
29
  assert @sinks.empty?
30
30
  assert_equal ["ERR default not found"], @io.to_a
31
31
  end
32
32
 
33
33
  def test_env
34
- sink_handler(@io, %w(ed default env.FOO=bar))
34
+ dpc_sink(@io, %w(ed default env.FOO=bar))
35
35
  assert_equal "bar", @sinks["default"].env["FOO"]
36
- sink_handler(@io, %w(ed default env.FOO=))
36
+ dpc_sink(@io, %w(ed default env.FOO=))
37
37
  assert_equal "", @sinks["default"].env["FOO"]
38
- sink_handler(@io, %w(ed default env#FOO))
38
+ dpc_sink(@io, %w(ed default env#FOO))
39
39
  assert_nil @sinks["default"].env["FOO"]
40
40
  end
41
41
 
@@ -47,12 +47,12 @@ def test_sink_ed
47
47
  --disable-format --disable-resample --disable-channels \
48
48
  -t raw -c $SINK_CHANNELS -f S${SINK_BITS}_3LE -r $SINK_RATE
49
49
  '
50
- sink_handler(@io, %W(ed foo command=#{command}))
50
+ dpc_sink(@io, %W(ed foo command=#{command}))
51
51
  assert_equal command, @sinks["foo"].command
52
52
  assert_empty @sinks["foo"].env
53
- sink_handler(@io, %W(ed foo env.SINK_BITS=24))
54
- sink_handler(@io, %W(ed foo env.SINK_CHANNELS=2))
55
- sink_handler(@io, %W(ed foo env.SINK_RATE=48000))
53
+ dpc_sink(@io, %W(ed foo env.SINK_BITS=24))
54
+ dpc_sink(@io, %W(ed foo env.SINK_CHANNELS=2))
55
+ dpc_sink(@io, %W(ed foo env.SINK_RATE=48000))
56
56
  expect = {
57
57
  "SINK_BITS" => "24",
58
58
  "SINK_CHANNELS" => "2",
@@ -68,7 +68,7 @@ def test_cat
68
68
  sink.name = "default"
69
69
  sink.command += "dither -s"
70
70
  @sinks["default"] = sink
71
- sink_handler(@io, %W(cat default))
71
+ dpc_sink(@io, %W(cat default))
72
72
  assert_equal 1, @io.size
73
73
  hsh = YAML.load(@io[0])
74
74
  assert_kind_of Hash, hsh
@@ -79,12 +79,12 @@ def test_cat
79
79
  def test_ls
80
80
  expect = %w(a b c d)
81
81
  expect.each { |s| @sinks[s] = true }
82
- sink_handler(@io, %W(ls))
82
+ dpc_sink(@io, %W(ls))
83
83
  assert_equal expect, Shellwords.split(@io[0])
84
84
  end
85
85
 
86
86
  def test_env_dump
87
- env_handler(@io, [])
87
+ dpc_env(@io, [])
88
88
  res = @io[0]
89
89
  result = {}
90
90
  Shellwords.split(res).each do |kv|
@@ -70,35 +70,6 @@ def test_example
70
70
 
71
71
  cmp = "cmp result.s32 expect.s32"
72
72
  assert system(cmp), cmp
73
-
74
- # try Ogg Opus, use opusenc/opusdec for now since that's available
75
- # in Debian 7.0 (sox.git currently has opusfile support, but that
76
- # hasn't made it into Debian, yet)
77
- if `which opusenc 2>/dev/null`.size > 0 &&
78
- `which opusdec 2>/dev/null`.size > 0
79
- WAIT_ALL_MTX.synchronize do
80
- tmp_err('opus.err.txt') { sfx.run("opusenc", opts) }
81
- end
82
- assert_contains_stats('opus.err.txt')
83
-
84
- # ensure opus lengths match flac ones, we decode using opusdec
85
- # since sox does not yet have opus support in Debian 7.0
86
- %w(1 2).each do |nr|
87
- cmd = "opusdec #{nr}.opus #{nr}.wav 2>/dev/null"
88
- assert system(cmd), cmd
89
- assert_equal `soxi -D #{nr}.flac`, `soxi -D #{nr}.wav`
90
- end
91
-
92
- # ensure 16/44.1kHz FLAC works (CDDA-like)
93
- File.unlink('1.flac', '2.flac')
94
- WAIT_ALL_MTX.synchronize do
95
- tmp_err('flac-cdda.err.txt') { sfx.run("flac-cdda", opts) }
96
- end
97
- assert_contains_stats('flac-cdda.err.txt')
98
- %w(1 2).each do |nr|
99
- assert_equal `soxi -D #{nr}.flac`, `soxi -D #{nr}.wav`
100
- end
101
- end
102
73
  end
103
74
  end
104
75
  end
@@ -3,10 +3,19 @@
3
3
  require_relative 'helper'
4
4
  require 'dtas/tracklist'
5
5
  class TestTracklist < Testcase
6
+
7
+ def list_to_path(tl)
8
+ tl.instance_variable_get(:@list).map(&:to_path)
9
+ end
10
+
11
+ def list_add(tl, ary)
12
+ ary.reverse_each { |x| tl.add_track(x) }
13
+ end
14
+
6
15
  def test_tl_add_tracks
7
16
  tl = DTAS::Tracklist.new
8
17
  tl.add_track("/foo.flac")
9
- assert_equal(%w(/foo.flac), tl.instance_variable_get(:@list))
18
+ assert_equal(%w(/foo.flac), list_to_path(tl))
10
19
 
11
20
  oids = tl.tracks
12
21
  assert_kind_of Array, oids
@@ -14,26 +23,24 @@ def test_tl_add_tracks
14
23
  assert_equal [ [ oids[0], "/foo.flac" ] ], tl.get_tracks(oids)
15
24
 
16
25
  tl.add_track("/bar.flac")
17
- assert_equal(%w(/bar.flac /foo.flac), tl.instance_variable_get(:@list))
26
+ assert_equal(%w(/bar.flac /foo.flac), list_to_path(tl))
18
27
 
19
28
  tl.add_track("/after.flac", oids[0])
20
- assert_equal(%w(/bar.flac /foo.flac /after.flac),
21
- tl.instance_variable_get(:@list))
29
+ assert_equal(%w(/bar.flac /foo.flac /after.flac), list_to_path(tl))
22
30
  end
23
31
 
24
32
  def test_add_current
25
33
  tl = DTAS::Tracklist.new
26
- tl.instance_variable_get(:@list).replace(%w(a b c d e f g))
34
+ list_add(tl, %w(a b c d e f g))
27
35
  tl.add_track('/foo.flac', nil, true)
28
- assert_equal '/foo.flac', tl.cur_track
36
+ assert_equal '/foo.flac', tl.cur_track.to_path
29
37
  end
30
38
 
31
39
  def test_advance_track
32
40
  tl = DTAS::Tracklist.new
33
- tl.instance_variable_get(:@list).replace(%w(a b c d e f g))
34
- %w(a b c d e f g).each do |t|
35
- assert_equal t, tl.advance_track[0]
36
- end
41
+ ary = %w(a b c d e f g)
42
+ list_add(tl, ary)
43
+ ary.each { |t| assert_equal t, tl.advance_track[0] }
37
44
  assert_nil tl.advance_track
38
45
  tl.repeat = true
39
46
  assert_equal 'a', tl.advance_track[0]
@@ -46,7 +53,7 @@ def _build_mapping(tl)
46
53
 
47
54
  def test_goto
48
55
  tl = DTAS::Tracklist.new
49
- tl.instance_variable_get(:@list).replace(%w(a b c d e f g))
56
+ list_add(tl, %w(a b c d e f g))
50
57
  mapping = _build_mapping(tl)
51
58
  assert_equal 'f', tl.go_to(mapping['f'])
52
59
  assert_equal 'f', tl.advance_track[0]
@@ -54,23 +61,74 @@ def test_goto
54
61
  assert_equal 'g', tl.advance_track[0]
55
62
  end
56
63
 
64
+ def test_shuffle
65
+ tl = DTAS::Tracklist.new
66
+ exp = %w(a b c d e f g)
67
+ list_add(tl, exp)
68
+ tl.shuffle = true
69
+ assert_equal(exp, list_to_path(tl))
70
+ assert_equal exp.size, tl.shuffle.size
71
+ assert_equal exp, tl.shuffle.map(&:to_path).sort
72
+ tl.shuffle = false
73
+ assert_equal false, tl.shuffle
74
+
75
+ tl.instance_variable_set :@pos, 3
76
+ assert_equal false, tl.shuffle = false
77
+
78
+ before = tl.cur_track
79
+ 3.times do
80
+ tl.shuffle = true
81
+ assert_equal before, tl.cur_track
82
+ end
83
+ x = tl.to_hsh
84
+ assert_equal true, x['shuffle']
85
+ 3.times do
86
+ loaded = DTAS::Tracklist.load(x.dup)
87
+ assert_equal before.to_path, loaded.cur_track.to_path
88
+ end
89
+ end
90
+
57
91
  def test_remove_track
58
92
  tl = DTAS::Tracklist.new
59
- tl.instance_variable_get(:@list).replace(%w(a b c d e f g))
93
+ ary = %w(a b c d e f g)
94
+ list_add(tl, ary)
60
95
  mapping = _build_mapping(tl)
61
- %w(a b c d e f g).each { |t| assert_kind_of Integer, mapping[t] }
96
+ ary.each { |t| assert_kind_of Integer, mapping[t] }
62
97
 
63
98
  tl.remove_track(mapping['a'])
64
- assert_equal %w(b c d e f g), tl.instance_variable_get(:@list)
99
+ assert_equal %w(b c d e f g), list_to_path(tl)
65
100
 
66
101
  tl.remove_track(mapping['d'])
67
- assert_equal %w(b c e f g), tl.instance_variable_get(:@list)
102
+ assert_equal %w(b c e f g), list_to_path(tl)
68
103
 
69
104
  tl.remove_track(mapping['g'])
70
- assert_equal %w(b c e f), tl.instance_variable_get(:@list)
105
+ assert_equal %w(b c e f), list_to_path(tl)
71
106
 
72
107
  # it'll be a while before OIDs require >128 bits, right?
73
108
  tl.remove_track(1 << 128)
74
- assert_equal %w(b c e f), tl.instance_variable_get(:@list), "no change"
109
+ assert_equal %w(b c e f), list_to_path(tl), "no change"
110
+ end
111
+
112
+ def test_max
113
+ tl = DTAS::Tracklist.new
114
+ assert_kind_of Integer, tl.add_track('z')
115
+ assert_kind_of Integer, tl.max
116
+ tl.max = 1
117
+ assert_equal false, tl.add_track('y')
118
+ assert_equal 1, tl.instance_variable_get(:@list).size
119
+ tl.max = 2
120
+ assert_kind_of Integer, tl.add_track('y')
121
+ assert_equal 2, tl.instance_variable_get(:@list).size
122
+ end
123
+
124
+ def test_swap
125
+ tl = DTAS::Tracklist.new
126
+ list_add(tl, %w(a b c d e f g))
127
+ mapping = _build_mapping(tl)
128
+ assert tl.swap(mapping['a'], mapping['g'])
129
+ assert_equal %w(g b c d e f a), list_to_path(tl)
130
+
131
+ assert_nil tl.swap(-2, -3), 'no swap on invalid'
132
+ assert_equal %w(g b c d e f a), list_to_path(tl)
75
133
  end
76
134
  end