#!/usr/bin/env python3 import subprocess import tempfile def main(): cargo_debug_build() cargo_test() test_example() cargo_build() test_fbas_analyzer() test_bulk_fbas_analyzer() test_qsc_simulator() print("All tests completed successfully!") def cargo_test(): run_and_check_return('cargo test --no-default-features', 'Running unit tests with minimal feature set') run_and_check_return('cargo test --all-features', 'Running unit tests with full feature set') run_and_check_return('cargo test -- --ignored', 'Running slow unit tests') def cargo_build(): run_and_check_return('cargo build --release --all-features', 'Building project to make sure we have up-to-date binaries') def cargo_debug_build(): run_and_check_return('cargo build', 'Building project to make sure we have up-to-date debug binaries for some tests') def test_example(): command = "cargo run --release --example results_reuse" expected_strings = [ 'c6602f930734bf9eb3dd35387aa0e8d0a31438ef57dbb4745e3ddfe6acf2b073', # standard form hash after reducing to core and removing inactive nodes 'GA35T3723UP2XJLC2H7MNL6VMKZZIFL2VW7XHMFFJKKIA2FJCYTLKFBW', # a minimal blocking set member ] run_and_check_output(command, expected_strings=expected_strings) def test_fbas_analyzer(): test_fbas_analyzer_with_ids() test_fbas_analyzer_on_broken() test_fbas_analyzer_on_mobilecoin() test_fbas_analyzer_with_organizations() def test_fbas_analyzer_with_organizations(): command = "target/release/fbas_analyzer test_data/stellarbeat_nodes_2019-09-17.json --merge-by-org test_data/stellarbeat_organizations_2019-09-17.json -a -p -S --only-core-nodes" expected_strings = [ 'has_quorum_intersection: true', 'minimal_quorums: [["Stellar Development Foundation","LOBSTR","SatoshiPay","COINQVEST Limited"],["Stellar Development Foundation","LOBSTR","SatoshiPay","Keybase"],["Stellar Development Foundation","LOBSTR","COINQVEST Limited","Keybase"],["Stellar Development Foundation","SatoshiPay","COINQVEST Limited","Keybase"],["LOBSTR","SatoshiPay","COINQVEST Limited","Keybase"]]', 'minimal_blocking_sets: [["Stellar Development Foundation","LOBSTR"],["Stellar Development Foundation","SatoshiPay"],["Stellar Development Foundation","COINQVEST Limited"],["Stellar Development Foundation","Keybase"],["LOBSTR","SatoshiPay"],["LOBSTR","COINQVEST Limited"],["LOBSTR","Keybase"],["SatoshiPay","COINQVEST Limited"],["SatoshiPay","Keybase"],["COINQVEST Limited","Keybase"]]', 'minimal_splitting_sets_with_affected_quorums:', '- ["Stellar Development Foundation","LOBSTR","COINQVEST Limited"]: [["Stellar Development Foundation","LOBSTR","SatoshiPay","COINQVEST Limited"],["Stellar Development Foundation","LOBSTR","COINQVEST Limited","Keybase"]]', 'top_tier: ["Stellar Development Foundation","LOBSTR","SatoshiPay","COINQVEST Limited","Keybase"]', ] run_and_check_output(command, expected_strings=expected_strings) def test_fbas_analyzer_with_ids(): command = "target/release/fbas_analyzer test_data/stellarbeat_nodes_2019-09-17.json -q" expected_strings = [ 'top_tier: [1,4,8,23,29,36,37,43,44,52,56,69,86,105,167,168,171]', ] run_and_check_output(command, expected_strings=expected_strings) def test_fbas_analyzer_on_broken(): command = "target/release/fbas_analyzer test_data/broken.json -a" expected_strings = [ 'has_quorum_intersection: false', 'minimal_blocking_sets: [[3,4],[4,10],[3,6,10]]', 'minimal_splitting_sets: [[]]', 'top_tier: [3,4,6,10]', ] run_and_check_output(command, expected_strings=expected_strings) def test_fbas_analyzer_on_mobilecoin(): command = "target/release/fbas_analyzer test_data/mobilecoin_nodes_2021-10-22.json" expected_strings = [ 'symmetric_clusters: [{"threshold":8,"validators":[0,1,2,3,4,5,6,7,8,9]}]', ] run_and_check_output(command, expected_strings=expected_strings) def test_bulk_fbas_analyzer(): test_bulk_fbas_analyzer_to_stdout() test_bulk_fbas_analyzer_update_flag() def test_bulk_fbas_analyzer_to_stdout(): input_files = ['test_data/' + x for x in [ 'broken.json', 'correct.json', 'stellarbeat_nodes_2019-09-17.json', 'stellarbeat_nodes_2020-01-16_broken_by_hand.json', 'stellarbeat_organizations_2019-09-17.json', ]] command = 'target/release/bulk_fbas_analyzer --only-core-nodes ' + ' '.join(input_files) expected_strings = [ 'label,has_quorum_intersection,top_tier_size,mbs_min,mbs_max,mbs_mean,mss_min,mss_max,mss_mean,mq_min,mq_max,mq_mean,orgs_top_tier_size,orgs_mbs_min,orgs_mbs_max,orgs_mbs_mean,orgs_mss_min,orgs_mss_max,orgs_mss_mean,orgs_mq_min,orgs_mq_max,orgs_mq_mean,isps_top_tier_size,isps_mbs_min,isps_mbs_max,isps_mbs_mean,isps_mss_min,isps_mss_max,isps_mss_mean,isps_mq_min,isps_mq_max,isps_mq_mean,ctries_top_tier_size,ctries_mbs_min,ctries_mbs_max,ctries_mbs_mean,ctries_mss_min,ctries_mss_max,ctries_mss_mean,ctries_mq_min,ctries_mq_max,ctries_mq_mean,standard_form_hash,analysis_duration_mq,analysis_duration_mbs,analysis_duration_mss,analysis_duration_total', 'broken,false,4,2,3', 'correct,true,3,2,2,2.0,1,1,1.0,2,2,2.0,,,,,,,,,,,', '2019-09-17,true,17,4,5,4.689655172413793,3,3,3.0,8,9,8.930232558139535,5,2,2,2.0,3,3,3.0,4,4,4.0,,,,,,,,,,,3,1,1,1.0,1,1,1.0,1,1,1.0,6f73c7787f38fdde66470cc3b2e469e092c70f52823396ae13e52c9a561b20f5,0.', '2020-01-16_broken_by_hand,false,22,5,6,5.625,0,0,0.0,2,11,10.9413', ] run_and_check_output(command, expected_strings=expected_strings) def test_bulk_fbas_analyzer_update_flag(): input_files = ['test_data/' + x for x in [ 'broken.json', 'correct.json', 'stellarbeat_nodes_2020-01-16_broken_by_hand.json', ]] update_files = ['test_data/' + x for x in [ 'broken.json', 'correct.json', 'stellarbeat_nodes_2019-09-17.json', 'stellarbeat_nodes_2020-01-16_broken_by_hand.json', 'stellarbeat_organizations_2019-09-17.json', ]] tf = tempfile.NamedTemporaryFile('r+') daily_csv = tf.name command = 'target/release/bulk_fbas_analyzer --only-core-nodes ' + ' '.join(input_files) run_redirect_stdout_to_file_and_check_return(command, tf) command = 'target/release/bulk_fbas_analyzer --only-core-nodes ' + ' '.join(update_files) + ' -u -o ' + daily_csv expected_strings = [ 'label,has_quorum_intersection,top_tier_size,mbs_min,mbs_max,mbs_mean,mss_min,mss_max,mss_mean,mq_min,mq_max,mq_mean,orgs_top_tier_size,orgs_mbs_min,orgs_mbs_max,orgs_mbs_mean,orgs_mss_min,orgs_mss_max,orgs_mss_mean,orgs_mq_min,orgs_mq_max,orgs_mq_mean,isps_top_tier_size,isps_mbs_min,isps_mbs_max,isps_mbs_mean,isps_mss_min,isps_mss_max,isps_mss_mean,isps_mq_min,isps_mq_max,isps_mq_mean,ctries_top_tier_size,ctries_mbs_min,ctries_mbs_max,ctries_mbs_mean,ctries_mss_min,ctries_mss_max,ctries_mss_mean,ctries_mq_min,ctries_mq_max,ctries_mq_mean,standard_form_hash,analysis_duration_mq,analysis_duration_mbs,analysis_duration_mss,analysis_duration_total', 'broken,false,4,2,3', 'correct,true,3,2,2,2.0,1,1,1.0,2,2,2.0,,,,,,,,,,,', '2020-01-16_broken_by_hand,false,22,5,6,5.625,0,0,0.0,2,11,10.9413', '2019-09-17,true,17,4,5,4.689655172413793,3,3,3.0,8,9,8.930232558139535,5,2,2,2.0,3,3,3.0,4,4,4.0,,,,,,,,,,,3,1,1,1.0,1,1,1.0,1,1,1.0,6f73c7787f38fdde66470cc3b2e469e092c70f52823396ae13e52c9a561b20f5,0.', ] run_redirect_stdout_to_file_and_check_output(command, tf, expected_strings=expected_strings) tf.close() def test_qsc_simulator(): graph = '0|1|0\n0|2|0\n1|0|0\n1|2|0\n2|0|0\n2|1|0' command = 'target/release/qsc_simulator AllNeighbors -' expected = '\n'.join([ '[', ' {', ' "publicKey": "n0",', ' "quorumSet": {', ' "threshold": 3,', ' "validators": [', ' "n0",', ' "n1",', ' "n2"', ' ]', ' }', ' },', ' {', ' "publicKey": "n1",', ' "quorumSet": {', ' "threshold": 3,', ' "validators": [', ' "n0",', ' "n1",', ' "n2"', ' ]', ' }', ' },', ' {', ' "publicKey": "n2",', ' "quorumSet": {', ' "threshold": 3,', ' "validators": [', ' "n0",', ' "n1",', ' "n2"', ' ]', ' }', ' }', ']', ]) run_and_check_output(command, expected_strings=[expected], stdin=graph) def run_and_check_return(command, log_message, expected_returncode=0): print("%s: `%s`" % (log_message, command)) completed_process = subprocess.run(command, shell=True) assert completed_process.returncode == expected_returncode,\ "Expected return code '%d', got '%d'." % (expected_returncode, completed_process.returncode) def run_and_check_output(command, log_message='Running command', expected_strings=[], stdin=''): print("%s: `%s`" % (log_message, command)) if stdin: print("Feeding in via STDIN:\n'''\n%s\n'''" % stdin) completed_process = subprocess.run(command, input=stdin, capture_output=True, universal_newlines=True, shell=True) stdout = completed_process.stdout stderr = completed_process.stderr print("Checking output for expected strings...") for expected in expected_strings: assert expected in stdout, '\n'.join([ "Missing expected output string:", "'''", expected, "'''", "Full output:", "'''", stdout + "'''", "STDERR: '%s'" % stderr, ]) def run_redirect_stdout_to_file_and_check_return(command, file_descriptor, expected_returncode=0): completed_process = subprocess.run(command, universal_newlines=True, shell=True, stdout=file_descriptor) assert completed_process.returncode == expected_returncode,\ "Expected return code '%d', got '%d'." % (expected_returncode, completed_process.returncode) def run_redirect_stdout_to_file_and_check_output(command, file_descriptor, log_message='Running command', expected_strings=[]): print("%s: `%s`" % (log_message, command)) completed_process = subprocess.run(command, universal_newlines=True, shell=True, stdout=file_descriptor) stderr = completed_process.stderr file_descriptor.seek(0) actual_strings = file_descriptor.read() print("Checking output for expected strings...") for expected in expected_strings: assert expected in actual_strings, '\n'.join([ "Missing expected output string:", "'''", expected, "'''", "Full output:", "'''", str(actual_strings) + "\n" + "'''" "STDERR: '%s'" % stderr, ]) if __name__ == "__main__": main()