GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

test_v3bwfile.py 21.6 KB
Newer Older
juga  's avatar
juga committed
1 2
# -*- coding: utf-8 -*-
"""Test generation of bandwidth measurements document (v3bw)"""
juga  's avatar
juga committed
3
import json
4
import logging
5
import math
juga  's avatar
juga committed
6
import os.path
7
from unittest import mock
juga  's avatar
juga committed
8

9
from sbws import __version__ as version
10
from sbws.globals import (SPEC_VERSION, SBWS_SCALING, TORFLOW_SCALING,
11
                          MIN_REPORT, TORFLOW_ROUND_DIG, PROP276_ROUND_DIG)
12
from sbws.lib.resultdump import Result, load_result_file, ResultSuccess
13 14 15 16
from sbws.lib.v3bwfile import (
    V3BWHeader, V3BWLine, TERMINATOR, LINE_SEP,
    KEYVALUE_SEP_V1, num_results_of_type,
    V3BWFile, round_sig_dig,
17
    HEADER_RECENT_MEASUREMENTS_EXCLUDED_KEYS
18
    )
juga  's avatar
juga committed
19
from sbws.util.timestamp import now_fname, now_isodt_str, now_unixts
juga  's avatar
juga committed
20

21
timestamp = 1523974147
22
timestamp_l = str(timestamp)
23
version_l = KEYVALUE_SEP_V1.join(['version', SPEC_VERSION])
24 25
scanner_country = 'US'
scanner_country_l = KEYVALUE_SEP_V1.join(['scanner_country', scanner_country])
26 27 28
destinations_countries = '00,DE'
destinations_countries_l = KEYVALUE_SEP_V1.join(['destinations_countries',
                                                destinations_countries])
29 30
software_l = KEYVALUE_SEP_V1.join(['software', 'sbws'])
software_version_l = KEYVALUE_SEP_V1.join(['software_version', version])
31
file_created = '2018-04-25T13:10:57'
32
file_created_l = KEYVALUE_SEP_V1.join(['file_created', file_created])
juga  's avatar
juga committed
33
latest_bandwidth = '2018-04-17T14:09:07'
34 35
latest_bandwidth_l = KEYVALUE_SEP_V1.join(['latest_bandwidth',
                                          latest_bandwidth])
36 37 38 39 40 41
attempts = '1'
attempts_l = KEYVALUE_SEP_V1.join(['recent_measurement_attempt_count',
                                   attempts])
failure = '0'
failure_l = KEYVALUE_SEP_V1.join(['recent_measurement_failure_count',
                                  failure])
42 43
header_ls = [timestamp_l, version_l, destinations_countries_l, file_created_l,
             latest_bandwidth_l,
44
             # attempts_l, failure_l,
45
             scanner_country_l, software_l, software_version_l, TERMINATOR]
46
header_str = LINE_SEP.join(header_ls) + LINE_SEP
47
earliest_bandwidth = '2018-04-16T14:09:07'
48 49
earliest_bandwidth_l = KEYVALUE_SEP_V1.join(['earliest_bandwidth',
                                            earliest_bandwidth])
50
generator_started = '2018-04-16T14:09:05'
51 52
generator_started_l = KEYVALUE_SEP_V1.join(['generator_started',
                                           generator_started])
53 54 55
tor_version = '0.4.2.5'
tor_version_l = KEYVALUE_SEP_V1.join(['tor_version', tor_version])

56
header_extra_ls = [timestamp_l, version_l,
57
                   earliest_bandwidth_l, file_created_l, generator_started_l,
juga  's avatar
juga committed
58
                   latest_bandwidth_l,
59 60
                   software_l, software_version_l, tor_version_l,
                   TERMINATOR]
61 62
header_extra_str = LINE_SEP.join(header_extra_ls) + LINE_SEP

63
# Line produced without any scaling.
64 65 66
# unmeasured and vote are not congruent with the exclusion,
# but `from_data` is only used in the test and doesn't include the
# arg `min_num`
67
raw_bwl_str = "bw=56 bw_mean=61423 bw_median=55656 "\
68
    "consensus_bandwidth=600000 consensus_bandwidth_is_unmeasured=False "\
69
    "desc_bw_avg=1000000000 desc_bw_bur=123456 desc_bw_obs_last=524288 "\
70 71
    "desc_bw_obs_mean=524288 error_circ=0 error_destination=0 error_misc=0 " \
    "error_second_relay=0 error_stream=1 " \
72
    "master_key_ed25519=g+Shk00y9Md0hg1S6ptnuc/wWKbADBgdjT0Kg+TSF3s " \
73
    "nick=A " \
74 75
    "node_id=$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA "\
    "relay_recent_measurement_attempt_count=2 "\
76
    "relay_recent_measurements_excluded_error_count=1 "\
77 78
    "relay_recent_priority_list_count=3 "\
    "rtt=456 success=1 " \
79
    "time=2018-04-17T14:09:07\n"
juga  's avatar
juga committed
80

81
v3bw_str = header_extra_str + raw_bwl_str
juga  's avatar
juga committed
82

juga  's avatar
juga committed
83 84 85

def test_v3bwheader_str():
    """Test header str"""
86
    header = V3BWHeader(timestamp_l, scanner_country=scanner_country,
87
                        destinations_countries=destinations_countries,
88
                        file_created=file_created)
89 90 91 92
    assert header_str == str(header)


def test_v3bwheader_extra_str():
93
    """Test header str with additional headers"""
94
    header = V3BWHeader(timestamp_l,
95 96
                        file_created=file_created,
                        generator_started=generator_started,
97 98
                        earliest_bandwidth=earliest_bandwidth,
                        tor_version=tor_version)
99 100 101 102
    assert header_extra_str == str(header)


def test_v3bwheader_from_lines():
103
    header_obj = V3BWHeader(timestamp_l,
104 105
                            file_created=file_created,
                            generator_started=generator_started,
106 107
                            earliest_bandwidth=earliest_bandwidth,
                            tor_version=tor_version)
108
    header, _ = V3BWHeader.from_lines_v1(header_extra_ls)
109 110 111 112
    assert str(header_obj) == str(header)


def test_v3bwheader_from_text():
113
    header_obj = V3BWHeader(timestamp_l,
114 115
                            file_created=file_created,
                            generator_started=generator_started,
116 117
                            earliest_bandwidth=earliest_bandwidth,
                            tor_version=tor_version)
118
    header, _ = V3BWHeader.from_text_v1(header_extra_str)
119
    assert str(header_obj) == str(header)
juga  's avatar
juga committed
120 121


juga  's avatar
juga committed
122 123 124 125 126
def test_num_results_of_type(result_success, result_error_stream):
    assert num_results_of_type([result_success], 'success') == 1
    assert num_results_of_type([result_error_stream], 'success') == 0
    assert num_results_of_type([result_success], 'error-stream') == 0
    assert num_results_of_type([result_error_stream], 'error-stream') == 1
juga  's avatar
juga committed
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
def assert_round_sig_dig_any_digits(n, result):
    """Test that rounding n to any reasonable number of significant digits
       produces result."""
    max_digits_int64 = int(math.ceil(math.log10(2**64 - 1))) + 1
    for d in range(1, max_digits_int64 + 1):
        assert(round_sig_dig(n, digits=d) == result)


def assert_round_sig_dig_any_digits_error(n, elp_fraction=0.5):
    """Test that rounding n to any reasonable number of significant digits
       produces a result within elp_fraction * 10.0 ** -(digits - 1)."""
    max_digits_int64 = int(math.ceil(math.log10(2**64 - 1))) + 1
    for d in range(1, max_digits_int64 + 1):
        error_fraction = elp_fraction * (10.0 ** -(d - 1))
        # use ceil rather than round, to work around floating-point inaccuracy
        e = int(math.ceil(n * error_fraction))
        assert(round_sig_dig(n, digits=d) >= n - e)
        assert(round_sig_dig(n, digits=d) <= n + e)


def test_round_sig_dig():
    """Test rounding to a number of significant digits."""
    # Expected values
    assert(round_sig_dig(11, 1) == 10)
    assert(round_sig_dig(11, 2) == 11)

    assert(round_sig_dig(15, 1) == 20)
    assert(round_sig_dig(15, 2) == 15)

    assert(round_sig_dig(54, 1) == 50)
    assert(round_sig_dig(54, 2) == 54)

    assert(round_sig_dig(96, 1) == 100)
    assert(round_sig_dig(96, 2) == 96)

    assert(round_sig_dig(839, 1) == 800)
    assert(round_sig_dig(839, 2) == 840)
    assert(round_sig_dig(839, 3) == 839)

    assert(round_sig_dig(5789, 1) == 6000)
    assert(round_sig_dig(5789, 2) == 5800)
    assert(round_sig_dig(5789, 3) == 5790)
    assert(round_sig_dig(5789, 4) == 5789)

    assert(round_sig_dig(24103, 1) == 20000)
    assert(round_sig_dig(24103, 2) == 24000)
    assert(round_sig_dig(24103, 3) == 24100)
    assert(round_sig_dig(24103, 4) == 24100)
    assert(round_sig_dig(24103, 5) == 24103)

179 180
    assert(round_sig_dig(300000, 1) == 300000)

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
    # Floating-point values

    # Must round based on fractions, must not double-round
    assert(round_sig_dig(14, 1) == 10)
    assert(round_sig_dig(14.0, 1) == 10)
    assert(round_sig_dig(14.9, 1) == 10)
    assert(round_sig_dig(15.0, 1) == 20)
    assert(round_sig_dig(15.1, 1) == 20)

    assert(round_sig_dig(14, 2) == 14)
    assert(round_sig_dig(14.0, 2) == 14)
    assert(round_sig_dig(14.9, 2) == 15)
    assert(round_sig_dig(15.0, 2) == 15)
    assert(round_sig_dig(15.1, 2) == 15)

    # Must round to integer
    assert(round_sig_dig(14, 3) == 14)
    assert(round_sig_dig(14.0, 3) == 14)
    assert(round_sig_dig(14.9, 3) == 15)
    assert(round_sig_dig(15.0, 3) == 15)
    assert(round_sig_dig(15.1, 3) == 15)

    # Small integers
    assert_round_sig_dig_any_digits(0, 1)
    assert_round_sig_dig_any_digits(1, 1)
    assert_round_sig_dig_any_digits(2, 2)
207 208 209 210 211 212
    assert_round_sig_dig_any_digits(3, 3)
    assert_round_sig_dig_any_digits(4, 4)
    assert_round_sig_dig_any_digits(5, 5)
    assert_round_sig_dig_any_digits(6, 6)
    assert_round_sig_dig_any_digits(7, 7)
    assert_round_sig_dig_any_digits(8, 8)
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
    assert_round_sig_dig_any_digits(9, 9)
    assert_round_sig_dig_any_digits(10, 10)

    # Large values
    assert_round_sig_dig_any_digits_error(2**30)
    assert_round_sig_dig_any_digits_error(2**31)
    assert_round_sig_dig_any_digits_error(2**32)

    # the floating-point accuracy limit for this function is 2**73
    # on some machines
    assert_round_sig_dig_any_digits_error(2**62)
    assert_round_sig_dig_any_digits_error(2**63)
    assert_round_sig_dig_any_digits_error(2**64)

    # Out of range values: must round to 1
    assert_round_sig_dig_any_digits(-0.01, 1)
    assert_round_sig_dig_any_digits(-1, 1)
    assert_round_sig_dig_any_digits(-10.5, 1)
    assert_round_sig_dig_any_digits(-(2**31), 1)

    # test the transition points in the supported range
    # testing the entire range up to 1 million takes 100s
    for n in range(1, 20000):
        assert_round_sig_dig_any_digits_error(n)

    # use a step that is relatively prime, to increase the chance of
    # detecting errors
    for n in range(90000, 200000, 9):
        assert_round_sig_dig_any_digits_error(n)

    for n in range(900000, 2000000, 99):
        assert_round_sig_dig_any_digits_error(n)


juga  's avatar
juga committed
247 248 249 250 251 252 253 254 255
def test_v3bwline_from_results_file(datadir):
    lines = datadir.readlines('results.txt')
    d = dict()
    for line in lines:
        r = Result.from_dict(json.loads(line.strip()))
        fp = r.fingerprint
        if fp not in d:
            d[fp] = []
        d[fp].append(r)
256
    bwl, _ = V3BWLine.from_data(d, fp)
juga  's avatar
juga committed
257 258
    # bw store now B, not KB
    bwl.bw = round(bwl.bw / 1000)
259
    assert raw_bwl_str == str(bwl)
juga  's avatar
juga committed
260 261


juga  's avatar
juga committed
262
def test_from_results_read(datadir, tmpdir, conf, args):
juga  's avatar
juga committed
263 264 265 266
    results = load_result_file(str(datadir.join("results.txt")))
    expected_header = V3BWHeader(timestamp_l,
                                 earliest_bandwidth=earliest_bandwidth,
                                 latest_bandwidth=latest_bandwidth)
267
    exclusion_dict = dict(
268
        [(k, 0) for k in HEADER_RECENT_MEASUREMENTS_EXCLUDED_KEYS]
269 270 271
        )
    expected_header.add_relays_excluded_counters(exclusion_dict)
    raw_bwls = [V3BWLine.from_results(results[fp])[0] for fp in results]
272 273 274
    # Scale BWLines using torflow method, since it's the default and BWLines
    # bandwidth is the raw bandwidth.
    expected_bwls = V3BWFile.bw_torflow_scale(raw_bwls)
275 276
    # Since the scaled lines will be less than the 60% relays in the network,
    # set under_min_report.
juga  's avatar
juga committed
277 278
    expected_bwls[0].under_min_report = 1
    expected_bwls[0].vote = 0
juga  's avatar
juga committed
279
    expected_f = V3BWFile(expected_header, expected_bwls)
juga  's avatar
juga committed
280 281
    # This way is going to convert bw to KB
    v3bwfile = V3BWFile.from_results(results)
juga  's avatar
juga committed
282
    assert str(expected_f)[1:] == str(v3bwfile)[1:]
juga  's avatar
juga committed
283
    output = os.path.join(args.output, now_fname())
juga  's avatar
juga committed
284 285 286
    v3bwfile.write(output)


juga  's avatar
juga committed
287
def test_from_arg_results_write(datadir, tmpdir, conf, args):
juga  's avatar
juga committed
288
    results = load_result_file(str(datadir.join("results.txt")))
juga  's avatar
juga committed
289
    v3bwfile = V3BWFile.from_results(results)
juga  's avatar
juga committed
290
    output = os.path.join(args.output, now_fname())
juga  's avatar
juga committed
291 292
    v3bwfile.write(output)
    assert os.path.isfile(output)
293 294 295 296


def test_from_arg_results_write_read(datadir, tmpdir, conf, args):
    results = load_result_file(str(datadir.join("results.txt")))
juga  's avatar
juga committed
297
    v3bwfile = V3BWFile.from_results(results)
298 299 300 301 302
    output = os.path.join(args.output, now_fname())
    v3bwfile.write(output)
    with open(output) as fd:
        v3bw = fd.read()
    assert v3bw == str(v3bwfile)
303 304 305 306 307 308 309 310


def test_sbws_scale(datadir):
    results = load_result_file(str(datadir.join("results.txt")))
    v3bwfile = V3BWFile.from_results(results, scaling_method=SBWS_SCALING)
    assert v3bwfile.bw_lines[0].bw == 8


311 312 313 314 315 316 317 318 319 320 321
def num_consensus_relays(fpath):
    return 1


# To do not have to create a consensus-cache file and set the path,
# mock the result since it only returns the number of relays.
@mock.patch.object(V3BWFile, 'read_number_consensus_relays')
def test_torflow_scale(mock_consensus, datadir, tmpdir, conf):
    mock_consensus.return_value = 1
    # state_fpath = str(tmpdir.join('.sbws', 'state.dat'))
    state_fpath = conf['paths']['state_fpath']
322
    results = load_result_file(str(datadir.join("results.txt")))
323 324 325 326 327 328 329
    # Since v1.1.0, it'll write bw=1 if the minimum percent of measured relays
    # wasn't reached. Therefore mock the consensus number.
    # Because the consensus number is mocked, it'll try to read the sate path.
    # Obtain it from conf, so that the root directory exists.
    v3bwfile = V3BWFile.from_results(results, '', '',
                                     state_fpath,
                                     scaling_method=TORFLOW_SCALING,
330
                                     round_digs=TORFLOW_ROUND_DIG)
juga  's avatar
juga committed
331
    assert v3bwfile.bw_lines[0].bw == 123
332 333 334
    v3bwfile = V3BWFile.from_results(results, '', '',
                                     state_fpath,
                                     scaling_method=TORFLOW_SCALING,
335 336
                                     torflow_cap=0.0001,
                                     round_digs=TORFLOW_ROUND_DIG)
juga  's avatar
juga committed
337
    assert v3bwfile.bw_lines[0].bw == 123
338 339 340
    v3bwfile = V3BWFile.from_results(results, '', '',
                                     state_fpath,
                                     scaling_method=TORFLOW_SCALING,
341 342
                                     torflow_cap=1,
                                     round_digs=TORFLOW_ROUND_DIG)
juga  's avatar
juga committed
343
    assert v3bwfile.bw_lines[0].bw == 123
344 345 346
    v3bwfile = V3BWFile.from_results(results, '', '',
                                     state_fpath,
                                     scaling_method=TORFLOW_SCALING,
347 348
                                     torflow_cap=1,
                                     round_digs=PROP276_ROUND_DIG)
juga  's avatar
juga committed
349
    assert v3bwfile.bw_lines[0].bw == 120
350 351 352 353 354 355 356 357


def test_results_away_each_other(datadir):
    min_num = 2
    secs_away = 86400  # 1d
    results = load_result_file(str(datadir.join("results_away.txt")))
    # A has 4 results, 3 are success, 2 are 1 day away, 1 is 12h away
    values = results["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]
358 359 360

    # There is one result excluded, but the relay is not excluded
    bwl, reason = V3BWLine.from_results(values, secs_away=secs_away, min_num=2)
361
    assert bwl.relay_recent_measurements_excluded_error_count == 1
362
    assert reason is None
363 364
    assert not hasattr(bwl, "vote")
    assert not hasattr(bwl, "unmeasured")
365

366 367 368 369
    success_results = [r for r in values if isinstance(r, ResultSuccess)]
    assert len(success_results) >= min_num
    results_away = V3BWLine.results_away_each_other(success_results, secs_away)
    assert len(results_away) == 3
370

371 372
    # B has 2 results, 12h away from each other
    values = results["BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"]
373 374 375 376

    # Two measurements are excluded and there were only 2,
    # the relay is excluded
    bwl, reason = V3BWLine.from_results(values, secs_away=secs_away, min_num=2)
377
    assert bwl.relay_recent_measurements_excluded_near_count == 2
378
    assert reason == 'recent_measurements_excluded_near_count'
juga  's avatar
juga committed
379 380
    assert bwl.vote == 0
    assert bwl.unmeasured == 1
381

382 383 384
    success_results = [r for r in values if isinstance(r, ResultSuccess)]
    assert len(success_results) >= min_num
    results_away = V3BWLine.results_away_each_other(success_results, secs_away)
385
    assert not results_away
386

387 388 389 390 391 392
    secs_away = 43200  # 12h
    values = results["BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"]
    success_results = [r for r in values if isinstance(r, ResultSuccess)]
    assert len(success_results) >= min_num
    results_away = V3BWLine.results_away_each_other(success_results, secs_away)
    assert len(results_away) == 2
393

394 395
    # C has 1 result
    values = results["CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"]
396 397 398

    # There is only 1 result, the relay is excluded
    bwl, reason = V3BWLine.from_results(values, min_num=2)
399
    assert bwl.relay_recent_measurements_excluded_few_count == 1
400
    assert reason == 'recent_measurements_excluded_few_count'
juga  's avatar
juga committed
401 402
    assert bwl.vote == 0
    assert bwl.unmeasured == 1
403

404 405
    success_results = [r for r in values if isinstance(r, ResultSuccess)]
    assert len(success_results) < min_num
406 407 408


def test_measured_progress_stats(datadir):
juga  's avatar
juga committed
409
    number_consensus_relays = 3
410
    bw_lines_raw = []
juga  's avatar
juga committed
411 412 413 414 415
    statsd_exp = {'percent_eligible_relays': 100,
                  'minimum_percent_eligible_relays': 60,
                  'number_consensus_relays': 3,
                  'minimum_number_eligible_relays': 2,
                  'number_eligible_relays': 3}
416 417 418 419
    min_perc_reached_before = None
    results = load_result_file(str(datadir.join("results_away.txt")))
    for fp, values in results.items():
        # log.debug("Relay fp %s", fp)
420
        line, _ = V3BWLine.from_results(values)
421 422 423 424 425
        if line is not None:
            bw_lines_raw.append(line)
    assert len(bw_lines_raw) == 3
    bw_lines = V3BWFile.bw_torflow_scale(bw_lines_raw)
    assert len(bw_lines) == 3
juga  's avatar
juga committed
426
    statsd, success = V3BWFile.measured_progress_stats(
427
        len(bw_lines), number_consensus_relays, min_perc_reached_before)
428 429
    assert success
    assert statsd == statsd_exp
juga  's avatar
juga committed
430
    number_consensus_relays = 6
juga  's avatar
juga committed
431
    statsd, success = V3BWFile.measured_progress_stats(
432
        len(bw_lines), number_consensus_relays, min_perc_reached_before)
433
    assert not success
juga  's avatar
juga committed
434 435 436 437 438
    statsd_exp = {'percent_eligible_relays': 50,
                  'minimum_percent_eligible_relays': 60,
                  'number_consensus_relays': 6,
                  'minimum_number_eligible_relays': 4,
                  'number_eligible_relays': 3}
439
    assert statsd_exp == statsd
juga  's avatar
juga committed
440 441 442 443


def test_update_progress(datadir, tmpdir):
    bw_lines_raw = []
juga  's avatar
juga committed
444
    number_consensus_relays = 6
juga  's avatar
juga committed
445 446 447 448 449 450 451 452 453
    state = {}
    header = V3BWHeader(str(now_unixts()))
    results = load_result_file(str(datadir.join("results_away.txt")))
    for fp, values in results.items():
        # log.debug("Relay fp %s", fp)
        line = V3BWLine.from_results(values)
        if line is not None:
            bw_lines_raw.append(line)
    bwfile = V3BWFile(header, [])
454
    bwfile.update_progress(len(bw_lines_raw), header, number_consensus_relays,
juga  's avatar
juga committed
455 456
                           state)
    assert header.percent_eligible_relays == '50'
juga  's avatar
juga committed
457
    assert state.get('min_perc_reached') is None
458 459
    # Test that the headers are also included when there are enough eligible
    # relays
juga  's avatar
juga committed
460
    number_consensus_relays = 3
juga  's avatar
juga committed
461
    header = V3BWHeader(str(now_unixts()))
462
    bwfile.update_progress(len(bw_lines_raw), header, number_consensus_relays,
juga  's avatar
juga committed
463
                           state)
juga  's avatar
juga committed
464
    assert state.get('min_perc_reached') == now_isodt_str()
465 466 467 468 469
    assert header.minimum_number_eligible_relays == '2'
    assert header.minimum_percent_eligible_relays == str(MIN_REPORT)
    assert header.number_consensus_relays == '3'
    assert header.number_eligible_relays == '3'
    assert header.percent_eligible_relays == '100'
470 471 472 473 474 475 476 477 478 479 480 481 482 483


def test_time_measure_half_network(caplog):
    header = V3BWHeader(timestamp_l,
                        file_created=file_created,
                        generator_started=generator_started,
                        earliest_bandwidth=earliest_bandwidth)
    header.number_consensus_relays = '6500'
    header.number_eligible_relays = '4000'
    caplog.set_level(logging.INFO)
    header.add_time_report_half_network()
    assert header.time_to_report_half_network == '70200'  # 19.5h
    expected_log = "Estimated time to measure the network: 39 hours."  # 19.5*2
    assert caplog.records[-1].getMessage() == expected_log
juga  's avatar
juga committed
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507


@mock.patch.object(V3BWFile, 'read_number_consensus_relays')
def test_set_under_min_report(mock_consensus, conf, datadir):
    # The number of relays (1) is the same as the ones in the consensus,
    # therefore there is no any relay excluded and under_min_report is not set.
    mock_consensus.return_value = 1
    state_fpath = conf['paths']['state_fpath']
    results = load_result_file(str(datadir.join("results.txt")))
    v3bwfile = V3BWFile.from_results(results, '', '', state_fpath)
    bwl = v3bwfile.bw_lines[0]
    assert not hasattr(bwl, "vote")
    assert not hasattr(bwl, "under_min_report")
    assert bwl.bw != 1

    # The number of relays is the same as the ones in the consensus,
    # but after filtering there's no any, under_min_report is set to 1
    # and unmeasured was also set to 1.
    # After filtering the relay is excluded because there's only 1 success
    # result and it should have at least 2 (min_num)
    v3bwfile = V3BWFile.from_results(results, '', '', state_fpath, min_num=2)
    bwl = v3bwfile.bw_lines[0]
    assert bwl.vote == 0
    assert bwl.under_min_report == 1
juga  's avatar
juga committed
508
    assert bwl.unmeasured == 1
juga  's avatar
juga committed
509 510 511 512 513 514 515 516 517 518
    assert bwl.bw == 1

    # The number of relays after scaling is than the 60% in the network,
    # therefore the relays are excluded and under_min_report is set to 1.
    mock_consensus.return_value = 3
    v3bwfile = V3BWFile.from_results(results, '', '', state_fpath)
    bwl = v3bwfile.bw_lines[0]
    assert bwl.vote == 0
    assert bwl.under_min_report == 1
    assert bwl.bw != 1
519 520 521 522 523 524 525 526 527 528 529 530 531


def test_generator_started(root_data_path, datadir):
    state_fpath = os.path.join(root_data_path, '.sbws/state.dat')
    # The method is correct
    assert "2019-03-25T13:03:06" == V3BWHeader.generator_started_from_file(
        state_fpath
    )
    # `results` does not matter here, using them to not have an empty list.
    results = load_result_file(str(datadir.join("results.txt")))
    header = V3BWHeader.from_results(results, '', '', state_fpath)
    # And the header is correct
    assert "2019-03-25T13:03:06" == header.generator_started