Skip to content

Commit 1f573e1

Browse files
Fix issue and add tests (#54)
* Record exclude_below_fees flag in output * Test exclude_below_fees in get_output_row * Test Theil with empty input * Test run * Fix bugs in test analyze * Test write_csv_output * Remove unused line * Add map tests * Test apply_mapping * Test fill_db_with_balances * Minor change for code clarity
1 parent 7fef203 commit 1f573e1

File tree

6 files changed

+251
-17
lines changed

6 files changed

+251
-17
lines changed

tests/test_analyze.py

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from tokenomics_decentralization.analyze import get_output_row, analyze_snapshot, analyze
1+
from tokenomics_decentralization.analyze import get_output_row, analyze_snapshot, analyze, write_csv_output
22
import pathlib
3+
import os
34

45

56
def test_get_output_row(mocker):
@@ -20,22 +21,28 @@ def test_get_output_row(mocker):
2021

2122
metrics = {'hhi': 1, 'gini': 0}
2223
csv_row = get_output_row('bitcoin', '2010-01-01', metrics)
23-
assert csv_row == ['bitcoin', '2010-01-01', False, False, 'absolute', 0, 1, 0]
24+
assert csv_row == ['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, 1, 0]
2425

2526
get_no_clustering_mock.return_value = True
2627
metrics = {'non-clustered hhi': 1, 'non-clustered gini': 0}
2728
csv_row = get_output_row('bitcoin', '2010-01-01', metrics)
28-
assert csv_row == ['bitcoin', '2010-01-01', True, False, 'absolute', 0, 1, 0]
29+
assert csv_row == ['bitcoin', '2010-01-01', True, False, 'absolute', 0, False, 1, 0]
2930

3031
get_exclude_contracts_mock.return_value = True
3132
metrics = {'exclude_contracts non-clustered hhi': 1, 'exclude_contracts non-clustered gini': 0}
3233
csv_row = get_output_row('bitcoin', '2010-01-01', metrics)
33-
assert csv_row == ['bitcoin', '2010-01-01', True, True, 'absolute', 0, 1, 0]
34+
assert csv_row == ['bitcoin', '2010-01-01', True, True, 'absolute', 0, False, 1, 0]
3435

3536
get_top_limit_value_mock.return_value = 1
3637
metrics = {'top-1_absolute exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_contracts non-clustered gini': 0}
3738
csv_row = get_output_row('bitcoin', '2010-01-01', metrics)
38-
assert csv_row == ['bitcoin', '2010-01-01', True, True, 'absolute', 1, 1, 0]
39+
assert csv_row == ['bitcoin', '2010-01-01', True, True, 'absolute', 1, False, 1, 0]
40+
41+
get_exclude_below_fees_mock.return_value = True
42+
get_top_limit_value_mock.return_value = 1
43+
metrics = {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_below_fees exclude_contracts non-clustered gini': 0}
44+
csv_row = get_output_row('bitcoin', '2010-01-01', metrics)
45+
assert csv_row == ['bitcoin', '2010-01-01', True, True, 'absolute', 1, True, 1, 0]
3946

4047

4148
def test_analyze_snapshot(mocker):
@@ -54,6 +61,9 @@ def test_analyze_snapshot(mocker):
5461
db_insert_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_metric')
5562
db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database')
5663

64+
compute_hhi_mock = mocker.patch('tokenomics_decentralization.analyze.compute_hhi')
65+
compute_tau_mock = mocker.patch('tokenomics_decentralization.analyze.compute_tau')
66+
5767
get_force_analyze_mock.return_value = False
5868
get_no_clustering_mock.return_value = False
5969
get_exclude_contracts_mock.return_value = False
@@ -88,41 +98,94 @@ def test_analyze_snapshot(mocker):
8898
db_insert_mock.return_value = None
8999
db_commit_mock.return_value = None
90100

101+
compute_hhi_mock.return_value = 2
91102
output = analyze_snapshot(None, 'bitcoin', '2010-01-01')
92-
assert output == {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 10000}
103+
assert output == {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 2}
93104

94105
get_no_clustering_mock.return_value = False
95106

107+
compute_hhi_mock.return_value = 3
96108
output = analyze_snapshot(None, 'bitcoin', '2010-01-01')
97-
assert output == {'top-1_absolute exclude_below_fees exclude_contracts hhi': 10000}
109+
assert output == {'top-1_absolute exclude_below_fees exclude_contracts hhi': 3}
98110

99111
get_top_limit_value_mock.return_value = 0
100112

113+
compute_hhi_mock.return_value = 4
101114
output = analyze_snapshot(None, 'bitcoin', '2010-01-01')
102-
assert output == {'exclude_below_fees exclude_contracts hhi': 5000}
115+
assert output == {'exclude_below_fees exclude_contracts hhi': 4}
103116

104117
get_top_limit_type_mock.return_value = 'percentage'
105118
get_top_limit_value_mock.return_value = 0.5
106119

120+
compute_hhi_mock.return_value = 5
107121
output = analyze_snapshot(None, 'bitcoin', '2010-01-01')
108-
assert output == {'top-0.5_percentage exclude_below_fees exclude_contracts hhi': 10000}
122+
assert output == {'top-0.5_percentage exclude_below_fees exclude_contracts hhi': 5}
109123

110124
get_top_limit_value_mock.return_value = 0
111125
get_metrics_mock.return_value = ['tau=0.5']
112126

127+
compute_tau_mock.return_value = [100, None]
113128
output = analyze_snapshot(None, 'bitcoin', '2010-01-01')
114-
assert output == {'exclude_below_fees exclude_contracts tau=0.5': 1}
129+
assert output == {'exclude_below_fees exclude_contracts tau=0.5': 100}
130+
131+
132+
def test_write_csv_output(mocker):
133+
get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics')
134+
get_metrics_mock.return_value = ['hhi']
135+
136+
get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories')
137+
get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve().parent]
138+
139+
get_no_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_no_clustering_flag')
140+
get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag')
141+
get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag')
142+
get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type')
143+
get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value')
144+
145+
get_no_clustering_mock.return_value = False
146+
get_exclude_contracts_mock.return_value = False
147+
get_exclude_below_fees_mock.return_value = False
148+
get_top_limit_type_mock.return_value = 'absolute'
149+
get_top_limit_value_mock.return_value = 0
150+
151+
write_csv_output([
152+
['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, 100],
153+
['ethereum', '2010-01-01', False, False, 'absolute', 0, False, 200],
154+
])
155+
with open(pathlib.Path(__file__).resolve().parent / 'output.csv') as f:
156+
lines = f.readlines()
157+
assert lines[0] == ','.join(['ledger', 'snapshot date', 'no_clustering', 'exclude_contract_addresses', 'top_limit_type', 'top_limit_value', 'exclude_below_fees', 'hhi']) + '\n'
158+
assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', '100']) + '\n'
159+
assert lines[2] == ','.join(['ethereum', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', '200']) + '\n'
160+
os.remove(pathlib.Path(__file__).resolve().parent / 'output.csv')
161+
162+
get_no_clustering_mock.return_value = True
163+
get_exclude_contracts_mock.return_value = True
164+
get_exclude_below_fees_mock.return_value = True
165+
get_top_limit_type_mock.return_value = 'absolute'
166+
get_top_limit_value_mock.return_value = 10
167+
168+
write_csv_output([
169+
['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, 100],
170+
['ethereum', '2010-01-01', False, False, 'absolute', 0, False, 200],
171+
])
172+
with open(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees.csv') as f:
173+
lines = f.readlines()
174+
assert lines[0] == ','.join(['ledger', 'snapshot date', 'no_clustering', 'exclude_contract_addresses', 'top_limit_type', 'top_limit_value', 'exclude_below_fees', 'hhi']) + '\n'
175+
assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', '100']) + '\n'
176+
assert lines[2] == ','.join(['ethereum', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', '200']) + '\n'
177+
os.remove(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees.csv')
115178

116179

117180
def test_analyze(mocker):
118181
get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories')
119-
get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve()]
182+
get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve().parent]
120183

121184
is_file_mock = mocker.patch('os.path.isfile')
122185
is_file_mock.return_value = True
123186

124-
get_db_connector_mock = mocker.patch('tokenomics_decentralization.schema.get_connector')
125-
get_db_connector_mock.return_value = None
187+
get_db_connector_mock = mocker.patch('tokenomics_decentralization.analyze.get_connector')
188+
get_db_connector_mock.return_value = 'connector'
126189

127190
analyze_snapshot_mock = mocker.patch('tokenomics_decentralization.analyze.analyze_snapshot')
128191
analyze_snapshot_mock.return_value = {'hhi': 1}
@@ -135,7 +198,7 @@ def test_analyze(mocker):
135198

136199
output_rows = analyze(['bitcoin'], ['2010-01-01'])
137200
assert output_rows == ['row']
138-
analyze_snapshot_mock.assert_called_with(None, 'bitcoin', '2010-01-01')
201+
analyze_snapshot_mock.assert_called_with('connector', 'bitcoin', '2010-01-01')
139202

140203
output_rows = analyze(['bitcoin', 'ethereum'], ['2010-01-01', '2011-01-01'])
141204
assert output_rows == ['row', 'row', 'row', 'row']

tests/test_map.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from tokenomics_decentralization.map import fill_db_with_addresses, apply_mapping, fill_db_with_balances
2+
from unittest.mock import call
3+
import pathlib
4+
5+
6+
def test_fill_db_with_addresses(mocker):
7+
db_insert_ledger_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_ledger')
8+
db_insert_ledger_mock.return_value = None
9+
db_insert_entity_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_entity')
10+
db_insert_entity_mock.return_value = None
11+
db_insert_update_address_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_update_address')
12+
db_insert_update_address_mock.return_value = None
13+
db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database')
14+
db_commit_mock.return_value = None
15+
16+
json_load_mock = mocker.patch('json.load')
17+
18+
json_load_mock.return_value = {
19+
'address 1': {'name': 'Entity Name 1', 'source': 'website'},
20+
'address 2': {'name': 'Entity Name 2', 'source': 'website', 'is_contract': True},
21+
}
22+
23+
fill_db_with_addresses('connector', 'random ledger')
24+
assert json_load_mock.call_args_list == []
25+
assert db_insert_entity_mock.call_args_list == []
26+
assert db_insert_update_address_mock.call_args_list == []
27+
assert db_commit_mock.call_args_list == []
28+
29+
fill_db_with_addresses('connector', 'bitcoin')
30+
db_insert_ledger_mock.assert_called_with('connector', 'bitcoin')
31+
assert call('connector', 'bitcoin', 'Entity Name 1') in db_insert_entity_mock.call_args_list
32+
assert call('connector', 'bitcoin', 'Entity Name 2') in db_insert_entity_mock.call_args_list
33+
assert call('connector', 'bitcoin', 'address 1', 'Entity Name 1', False) in db_insert_update_address_mock.call_args_list
34+
assert call('connector', 'bitcoin', 'address 2', 'Entity Name 2', True) in db_insert_update_address_mock.call_args_list
35+
db_commit_mock.assert_called()
36+
37+
38+
def test_fill_db_with_balances(mocker):
39+
get_input_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_input_directories')
40+
get_input_directories_mock.return_value = [pathlib.Path('/input').resolve()]
41+
42+
os_isfile_mock = mocker.patch('os.path.isfile')
43+
os_isfile_mock.return_value = True
44+
45+
db_insert_address_without_update_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_address_without_update')
46+
db_insert_snapshot_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_snapshot')
47+
db_insert_balance_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_balance')
48+
db_update_circulation_mock = mocker.patch('tokenomics_decentralization.db_helper.update_circulation')
49+
db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database')
50+
51+
mocker.patch('builtins.open', mocker.mock_open(read_data='address,type,balance\naddr1,pubkey,100\naddr2,pubkey,200'))
52+
53+
fill_db_with_balances('connector', 'bitcoin', '2010-01-01')
54+
assert db_insert_snapshot_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')]
55+
assert db_insert_address_without_update_mock.call_args_list == [call('connector', 'bitcoin', 'addr1'), call('connector', 'bitcoin', 'addr2')]
56+
assert db_insert_balance_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01', 'addr1', 100), call('connector', 'bitcoin', '2010-01-01', 'addr2', 200)]
57+
assert db_update_circulation_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01', 300)]
58+
assert db_commit_mock.call_args_list == [call()]
59+
60+
db_insert_address_without_update_mock.reset_mock()
61+
db_insert_snapshot_mock.reset_mock()
62+
db_insert_balance_mock.reset_mock()
63+
db_update_circulation_mock.reset_mock()
64+
db_commit_mock.reset_mock()
65+
66+
mocker.patch('builtins.open', mocker.mock_open(read_data='address,balance\naddr1,0\naddr2,1000000000'))
67+
68+
fill_db_with_balances('connector', 'ethereum', '2010-01-01')
69+
assert db_insert_snapshot_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01')]
70+
assert db_insert_address_without_update_mock.call_args_list == [call('connector', 'ethereum', 'addr2')]
71+
assert db_insert_balance_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01', 'addr2', 1)]
72+
assert db_update_circulation_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01', 1)]
73+
assert db_commit_mock.call_args_list == [call()]
74+
75+
76+
def test_apply_mapping(mocker):
77+
get_db_connector_mock = mocker.patch('tokenomics_decentralization.map.get_connector')
78+
get_db_connector_mock.return_value = 'connector'
79+
80+
fill_db_with_addresses_mock = mocker.patch('tokenomics_decentralization.map.fill_db_with_addresses')
81+
fill_db_with_balances_mock = mocker.patch('tokenomics_decentralization.map.fill_db_with_balances')
82+
83+
get_force_map_addresses_mock = mocker.patch('tokenomics_decentralization.helper.get_force_map_addresses_flag')
84+
get_force_map_balances_mock = mocker.patch('tokenomics_decentralization.helper.get_force_map_balances_flag')
85+
86+
get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories')
87+
get_input_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_input_directories')
88+
89+
os_isfile_mock = mocker.patch('os.path.isfile')
90+
os_isfile_mock.side_effect = {
91+
pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': False,
92+
pathlib.Path('/input1').resolve() / 'bitcoin_2010-01-01_raw_data.csv': False,
93+
pathlib.Path('/input2').resolve() / 'bitcoin_2010-01-01_raw_data.csv': False,
94+
}.get
95+
96+
get_force_map_addresses_mock.return_value = False
97+
get_force_map_balances_mock.return_value = False
98+
99+
get_output_directories_mock.return_value = [pathlib.Path('/output').resolve()]
100+
get_input_directories_mock.return_value = [pathlib.Path('/input1').resolve(), pathlib.Path('/input2').resolve()]
101+
102+
apply_mapping('bitcoin', '2010-01-01')
103+
assert get_db_connector_mock.call_args_list == []
104+
assert fill_db_with_addresses_mock.call_args_list == []
105+
assert fill_db_with_balances_mock.call_args_list == []
106+
107+
os_isfile_mock.side_effect = {
108+
pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': True,
109+
}.get
110+
get_input_directories_mock.reset_mock()
111+
112+
apply_mapping('bitcoin', '2010-01-01')
113+
assert get_input_directories_mock.call_args_list == []
114+
assert fill_db_with_addresses_mock.call_args_list == []
115+
assert fill_db_with_balances_mock.call_args_list == []
116+
117+
get_force_map_addresses_mock.return_value = True
118+
get_force_map_balances_mock.return_value = True
119+
120+
apply_mapping('bitcoin', '2010-01-01')
121+
assert fill_db_with_addresses_mock.call_args_list == [call('connector', 'bitcoin')]
122+
assert fill_db_with_balances_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')]
123+
124+
os_isfile_mock.side_effect = {
125+
pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': False,
126+
pathlib.Path('/input1').resolve() / 'bitcoin_2010-01-01_raw_data.csv': True
127+
}.get
128+
129+
get_force_map_addresses_mock.return_value = False
130+
get_force_map_balances_mock.return_value = False
131+
get_input_directories_mock.reset_mock()
132+
fill_db_with_addresses_mock.reset_mock()
133+
fill_db_with_balances_mock.reset_mock()
134+
135+
apply_mapping('bitcoin', '2010-01-01')
136+
assert get_input_directories_mock.call_args_list == [call()]
137+
assert fill_db_with_addresses_mock.call_args_list == [call('connector', 'bitcoin')]
138+
assert fill_db_with_balances_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')]

tests/test_metrics.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,7 @@ def test_compute_theil_index():
173173
tokens_per_entity = {('a', 432)}
174174
theil_t = compute_theil_index(tokens_per_entity, 432)
175175
assert round(theil_t, decimals) == 0
176+
177+
tokens_per_entity = {}
178+
theil_t = compute_theil_index(tokens_per_entity, 432)
179+
assert theil_t == 0

tests/test_run.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import run
2+
from unittest.mock import call
3+
4+
5+
def test_run(mocker):
6+
apply_mapping_mock = mocker.patch('run.apply_mapping')
7+
analyze_mock = mocker.patch('run.analyze')
8+
plot_mock = mocker.patch('run.plot')
9+
get_plot_flag_mock = mocker.patch('tokenomics_decentralization.helper.get_plot_flag')
10+
11+
get_plot_flag_mock.return_value = False
12+
13+
run.main(['bitcoin'], ['2010-01-01'])
14+
15+
apply_mapping_mock.assert_called_with('bitcoin', '2010-01-01')
16+
analyze_mock.assert_called_with(['bitcoin'], ['2010-01-01'])
17+
18+
run.main(['bitcoin', 'ethereum'], ['2010-01-01', '2020-01-01'])
19+
assert call('bitcoin', '2010-01-01') in apply_mapping_mock.call_args_list
20+
assert call('bitcoin', '2020-01-01') in apply_mapping_mock.call_args_list
21+
assert call('ethereum', '2010-01-01') in apply_mapping_mock.call_args_list
22+
assert call('ethereum', '2020-01-01') in apply_mapping_mock.call_args_list
23+
analyze_mock.assert_called_with(['bitcoin', 'ethereum'], ['2010-01-01', '2020-01-01'])
24+
25+
get_plot_flag_mock.return_value = True
26+
run.main(['bitcoin'], ['2010-01-01'])
27+
plot_mock.assert_called()

tokenomics_decentralization/analyze.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def get_output_row(ledger, date, metrics):
9595
top_limit_type = hlp.get_top_limit_type()
9696
top_limit_value = hlp.get_top_limit_value()
9797

98-
csv_row = [ledger, date, no_clustering, exclude_contract_addresses_flag, top_limit_type, top_limit_value]
98+
csv_row = [ledger, date, no_clustering, exclude_contract_addresses_flag, top_limit_type, top_limit_value, exclude_below_fees_flag]
9999

100100
for metric_name in hlp.get_metrics():
101101
val = metric_name
@@ -112,20 +112,23 @@ def get_output_row(ledger, date, metrics):
112112

113113

114114
def write_csv_output(output_rows):
115-
header = ['ledger', 'snapshot date', 'no_clustering', 'exclude_contract_addresses', 'top_limit_type', 'top_limit_value']
115+
header = ['ledger', 'snapshot date', 'no_clustering', 'exclude_contract_addresses', 'top_limit_type', 'top_limit_value', 'exclude_below_fees']
116116
header += hlp.get_metrics()
117117

118118
no_clustering = hlp.get_no_clustering_flag()
119119
exclude_contract_addresses_flag = hlp.get_exclude_contracts_flag()
120120
top_limit_type = hlp.get_top_limit_type()
121121
top_limit_value = hlp.get_top_limit_value()
122+
exclude_below_fees_flag = hlp.get_exclude_below_fees_flag()
122123
output_filename = 'output'
123124
if no_clustering:
124125
output_filename += '-no_clustering'
125126
if exclude_contract_addresses_flag:
126127
output_filename += '-exclude_contract_addresses'
127128
if top_limit_value:
128129
output_filename += f'-{top_limit_type}_{top_limit_value}'
130+
if exclude_below_fees_flag:
131+
output_filename += '-exclude_below_fees'
129132
output_filename += '.csv'
130133

131134
output_dir = hlp.get_output_directories()[0]

tokenomics_decentralization/map.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
def fill_db_with_addresses(conn, ledger):
1313
db_hlp.insert_ledger(conn, ledger)
14-
db_hlp.get_ledger_id(conn, ledger)
1514

1615
try:
1716
with open(hlp.MAPPING_INFO_DIR / f'addresses/{ledger}.json') as f:

0 commit comments

Comments
 (0)