File: tc_soak.rb

package info (click to toggle)
dnsruby 1.61.5-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,520 kB
  • sloc: ruby: 17,811; makefile: 3
file content (256 lines) | stat: -rw-r--r-- 8,153 bytes parent folder | download | duplicates (2)
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# --
# Copyright 2007 Nominet UK
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++

require_relative 'spec_helper'

# require_relative 'tc_single_resolver'
require_relative 'tc_soak_base'
require_relative 'test_dnsserver'
require_relative 'localdns'


# This class tries to soak test the Dnsruby library.
# It can't do this very well, owing to the small number of sockets allowed to be open simultaneously.
# @TODO@ Future versions of dnsruby will allow random streaming over a fixed number of (cycling) random sockets,
# so this test can be beefed up considerably at that point.

class TestSingleResolverSoak < Minitest::Test

  IP   = SimpleTCPPipeliningUDPServer::IP
  PORT = SimpleTCPPipeliningUDPServer::PORT

  def initialize(arg)
    super(arg)
  end

  def teardown
    Celluloid.shutdown
  end

  SINGLE_RESOLVER_QUERY_TIMES = 63

  def setup
    # Instantiate a local dns server
    pipe = IO.popen("./test/localdns.rb")
    @dnspid = pipe.pid
    sleep 1
  end

  def teardown
    Process.kill("KILL", @dnspid)
    sleep 1
  end

  def test_many_asynchronous_queries_one_single_resolver
    run_many_asynch_queries_test_single_res(1)
  end

  def test_many_asynchronous_queries_many_single_resolvers
    run_many_asynch_queries_test_single_res(50)
  end

  def test_many_asynchronous_queries_one_single_resolver_tcp
    run_many_asynch_queries_test_single_res(1, true)
  end

  def test_many_asynchronous_queries_many_single_resolvers_tcp
    run_many_asynch_queries_test_single_res(50, true)
  end

  def test_many_asynchronous_queries_one_single_resolver_tcp_pipelining
    run_many_asynch_queries_test_single_res(1, true, true)
  end

  def test_many_asynchronous_queries_many_single_resolvers_tcp_pipelining
    run_many_asynch_queries_test_single_res(50, true, true)
  end

  def run_many_asynch_queries_test_single_res(num_resolvers, tcp = false, pipelining = false)
    q = Queue.new
    timeout_count = 0
    resolvers = Array.new(num_resolvers) do
      Dnsruby::SingleResolver.new(server:                     IP,
                                  port:                       PORT,
                                  do_caching:                 false,
                                  do_validation:              false,
                                  tcp_pipelining:             pipelining,
                                  packet_timeout:             10,
                                  tcp_pipelining_max_queries: 5,
                                  use_tcp:                    tcp)
    end
    start = Time.now

    #  @todo@ On windows, MAX_FILES is 256. This means that we have to limit
    #  this test while we're not using single sockets.
    #  We run four queries per iteration, so we're limited to 64 runs.
    messages = TestSoakBase::Rrs.map do |data|
      message = Dnsruby::Message.new(data[:name], data[:type])
      message.do_validation = false
      message.do_caching    = false
      message
    end

    query_count = SINGLE_RESOLVER_QUERY_TIMES * messages.count

    receive_thread = Thread.new do
      query_count.times do
        _id, ret, error = q.pop
        if error.is_a?(Dnsruby::ResolvTimeout)
          timeout_count+=1
        elsif ret.class != Dnsruby::Message
          p "ERROR RETURNED : #{error}"
        end
      end
    end

    resolver_cycler = resolvers.cycle

    SINGLE_RESOLVER_QUERY_TIMES.times do |i|
      rr_count = 0
      messages.each do | message |
        rr_count += 1
        resolver_cycler.next.send_async(message, q, rr_count + i * messages.count)
        #         p "Sent #{i}, #{rr_count}, Queue #{q}"
      end
    end

    receive_thread.join

    time_taken = Time.now - start
    puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
    assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
  end

  def test_many_threads_on_one_single_resolver_synchronous
    #  Test multi-threaded behaviour
    #  Check the header IDs to make sure they're all different
    threads = Array.new

    res = create_default_single_resolver
    ids = []
    mutex = Mutex.new
    timeout_count = 0
    query_count = 0
    res.packet_timeout=4
    start=Time.now
    #  Windows limits us to 256 sockets
    num_times=250
    if (/java/ =~ RUBY_PLATFORM)
      #  JRuby threads are native threads, so let's not go too mad!
      num_times=50
    end
    num_times.times do |i|
      threads[i] = Thread.new{
        40.times do |j|
          TestSoakBase::Rrs.each do |data|
            mutex.synchronize { query_count += 1 }
            packet=nil
            begin
              packet = res.query(data[:name], data[:type])
            rescue Dnsruby::ResolvTimeout
              mutex.synchronize { timeout_count += 1 }
              next
            end
            assert(packet)
            ids.push(packet.header.id)
            assert_equal(packet.question[0].qclass,    'IN',             'Class correct'           )
          end
        end
      }
    end
    threads.each do |thread|
      thread.join
    end
    stop=Time.now
    time_taken=stop-start
    puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
    #     check_ids(ids) # only do this if we expect all different IDs - e.g. if we stream over a single socket
    assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
  end

  def check_ids(ids)
    ids.sort!
    count = 0
    ids.each do |id|
      count+=1
      if (count < ids.length-1)
        assert(ids[count+1] != id, "Two identical header ids used!")
      end
    end
  end

  def test_many_threads_on_many_single_resolvers
    #  Test multi-threaded behaviour
    #  @todo@ Check the header IDs to make sure they're all different
    threads = Array.new
    mutex = Mutex.new
    timeout_count = 0
    query_count = 0
    start=Time.now
    num_times=250
    if (/java/ =~ RUBY_PLATFORM)
      #  JRuby threads are native threads, so let's not go too mad!
      num_times=50
    end
    num_times.times do |i|
      threads[i] = Thread.new{
        res = create_default_single_resolver
        40.times do |j|
          TestSoakBase::Rrs.each do |data|
            mutex.synchronize do
              query_count+=1
            end
            q = Queue.new

            message = Dnsruby::Message.new(data[:name], data[:type])
            message.do_validation = false
            message.do_caching    = false

            res.send_async(message, q, [i,j])

            id, packet, error = q.pop
            if (error.class == Dnsruby::ResolvTimeout)
              mutex.synchronize {
                timeout_count+=1
              }
              next
            elsif (packet.class!=Dnsruby::Message)
              puts "ERROR! #{error}"
            end

            assert(packet)
            assert_equal(packet.question[0].qclass,    'IN',             'Class correct'           )
          end
        end
      }
    end
    # NOTE: For methods on the objects taking no params, we can use this shorthand.
    threads.each(&:join)

    time_taken = Time.now - start
    puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
    assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
  end

  def create_default_single_resolver
    Dnsruby::SingleResolver.new(server:         IP,
                                port:           PORT,
                                do_caching:     false,
                                do_validation:  false,
                                packet_timeout: 10)

  end
end