msgthr.git  about / heads / tags
non-recursive, container-agnostic message threading
blob 3d70d35c36b564152914913965420f3ced04e323 4565 bytes (raw)
$ git show HEAD:test/test_msgthr.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
 
# Copyright (C) 2016 all contributors <msgthr-public@80x24.org>
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
require 'test/unit'
require 'msgthr'

class TestMsgthr < Test::Unit::TestCase
  def test_msgthr
    thr = Msgthr.new
    parent_child = ''
    # Note that C is added after B,
    # hence it's message will be empty after adding B
    expected_parent_child = '->B'
    thr.add('a', %w(c b), 'abc')
    thr.add('b', %w(c), 'B') do |parent, child|
      parent_child = "#{parent.msg}->#{child.msg}"
    end
    assert_equal parent_child, expected_parent_child
    thr.add('c', nil, 'c')
    thr.add('D', nil, 'D')
    thr.add('d', %w(missing), 'd')
    rset = thr.thread!
    rootset = thr.order! { |c| c.sort_by!(&:mid) }
    assert_same rset, rootset
    assert_equal %w(D c missing), rootset.map(&:mid)
    assert_equal 'D', rootset[0].msg
    assert_equal %w(b), rootset[1].children.map(&:mid)
    out = ''.b
    thr.walk_thread do |level, container, index|
      msg = container.msg
      summary = msg ? msg : "[missing: <#{container.mid}>]"
      indent = '  ' * level
      out << sprintf("#{indent} % 3d. %s\n", index, summary)
    end
    exp = <<EOF.b
   0. D
   1. c
     0. B
       0. abc
   2. [missing: <missing>]
     0. d
EOF
    assert_equal exp, out
  end

  def test_order_in_thread
    thr = Msgthr.new
    thr.add(1, nil, 'a')
    thr.add(2, [1], 'b')
    thr.thread! do |ary|
      ary.sort_by! do |cont|
        cur = cont.topmost
        cur ? cur : 0
      end
    end
    out = ''
    thr.walk_thread do |level, container, index|
      msg = container.msg
      out << "#{level} [#{index}] #{msg}\n"
    end
    exp = <<EOF.b
0 [0] a
1 [0] b
EOF
    assert_equal exp, out
  end

  def test_out_of_order
    thr = Msgthr.new
    thr.thread!
    assert_raise(Msgthr::StateError) { thr.add(1, nil, 'a') }
    thr.clear # make things good again, following should not raise:
    thr.add(1, nil, 'a')
    thr.thread!
    assert_raise(Msgthr::StateError) { thr.thread! }

    out = []
    thr.walk_thread do |level, container, index|
      msg = container.msg
      out << "#{level} [#{index}] #{msg}"
    end
    assert_equal [ '0 [0] a' ], out
    assert_raise(Msgthr::StateError) { thr.thread! { raise "DO NOT CALL" } }
    assert_raise(Msgthr::StateError) { thr.order! { |_| raise "DO NOT CALL" } }

    # this is legal, even if non-sensical
    thr.clear
    thr.walk_thread { |level, container, index| raise "DO NOT CALL" }
  end

  def test_short_add_to_walk
    thr = Msgthr.new
    thr.add(1, nil, 'a')
    thr.add(2, [1], 'b')
    out = ''
    thr.walk_thread do |level, container, index|
      msg = container.msg
      out << "#{level} [#{index}] #{msg}\n"
    end
    exp = <<EOF.b
0 [0] a
1 [0] b
EOF
    assert_equal exp, out
  end

  def test_add_child_callback
    thr = Msgthr.new
    threads = {}
    [1, 11, 12, 2, 21, 211].each{ |id| threads[id] = [id]}
    my_add = lambda do |id, refs, msg|
      thr.add(id, refs, msg) do |parent, child|
        threads[child.mid] = threads[parent.mid]
      end
    end
    # Create the following structure
    # 1
    #  \
    #  | 1.1
    #  \
    #    1.2
    # 2
    #   \
    #    2.1
    #       \
    #         2.1.1
    my_add.call(1, nil, '1')
    my_add.call(11, [1], '1.1')
    my_add.call(12, [1], '1.2')
    my_add.call(2, nil, '2')
    my_add.call(21, [2], '2.1')
    my_add.call(211, [21], '2.1.1')

    thr.thread!
    thr.rootset.each do |cnt|
      threads[cnt.mid][0] = cnt.msg
    end

    assert_equal threads[1], threads[11]
    assert_equal threads[1], threads[12]
    assert_equal threads[2], threads[21]
    assert_equal threads[2], threads[211]
    assert_equal threads[21], threads[211]
    assert_equal threads[1][0], '1'
    assert_equal threads[2][0], '2'
  end

  def test_no_lost_root
    ids = [
      [ 8, [] ],
      [ 7, [8] ],
      [ 6, [8, 7] ],
      [ 3, [6, 7, 8] ],
      [ 2, [6, 7, 8, 3] ],
      [ 10, [8, 7, 6] ],
      [ 9, [6, 3] ],
      [ 5, [6, 7, 8, 3, 2] ],
      [ 4, [2, 3] ],
      [ 1, [2, 3, 4] ],
      [ 'a', ],
      [ 'b', ['a'] ],
    ]
    [ [ :forward, ids, 2 ],
      [ :backwards, ids.reverse, 3 ],
      [ :shuffle, ids.shuffle, nil ],
    ].each do |desc,msgs,exp|
      thr = Msgthr.new
      msgs.each { |id| thr.add(id[0], id[1], id[0]) }
      seen0 = nr = 0
      thr.walk_thread do |level, _, _|
        seen0 += 1 if level == 0
        nr += 1
      end
      assert_equal nr, msgs.size, 'no lost messages'
      assert_equal exp, seen0, "single root #{desc}" if exp
    end
  end
end

git clone https://80x24.org/msgthr.git