diff --git a/binarycpython/tests/main.py b/binarycpython/tests/main.py index 4f5e8a7835d69fa89b31814ff65f657fbc802d83..9348e885884c88aba4c1bc7deaaf21459f46b985 100755 --- a/binarycpython/tests/main.py +++ b/binarycpython/tests/main.py @@ -3,12 +3,14 @@ Main file for the tests. This file imports all the unit test functions from all modules except for the notebooks and for the HPC functions """ +# pylint: disable=W0611 + import unittest from binarycpython.tests.test_c_bindings import ( test_run_system, test_return_store_memaddr, - TestEnsemble, + test_ensemble_functions ) from binarycpython.tests.test_custom_logging import ( test_autogen_C_logging_code, @@ -19,7 +21,23 @@ from binarycpython.tests.test_custom_logging import ( test_create_and_load_logging_function ) # from binarycpython.tests.test_distributions import * -# from binarycpython.tests.test_functions import * +from binarycpython.tests.test_functions import ( + test_verbose_print, + test_temp_dir, + test_remove_file, + test_create_hdf5, + test_output_lines, + test_example_parse_output, + test_get_defaults, + test_get_arg_keys, + test_create_arg_string, + test_get_help, + test_get_help_all, + test_get_help_super, + test_make_build_text, + test_write_binary_c_parameter_descriptions_to_rst_file, + +) # from binarycpython.tests.test_grid import * # from binarycpython.tests.test_hpc_functions import * # from binarycpython.tests.test_plot_functions import * diff --git a/binarycpython/tests/test_c_bindings.py b/binarycpython/tests/test_c_bindings.py index b8ee8e1e5db86c313c80b2997aa25300bea1a34d..e51922448babb7a2cfb29bb16b6929003f2ecbb6 100644 --- a/binarycpython/tests/test_c_bindings.py +++ b/binarycpython/tests/test_c_bindings.py @@ -154,7 +154,7 @@ class test_return_store_memaddr(unittest.TestCase): ####################################################################################################################################################### -class TestEnsemble(unittest.TestCase): +class test_ensemble_functions(unittest.TestCase): """ Unittests for handling the ensemble outputs and adding those """ @@ -163,7 +163,7 @@ class TestEnsemble(unittest.TestCase): """ init function """ - super(TestEnsemble, self).__init__(*args, **kwargs) + super(test_ensemble_functions, self).__init__(*args, **kwargs) def test_return_persistent_data_memaddr(self): with Capturing() as output: diff --git a/binarycpython/tests/test_functions.py b/binarycpython/tests/test_functions.py index 0f3bec3a50a28a5a73040cffca96e8c099bbf14e..924961a2e41a162a30a882c96d1db00286086581 100644 --- a/binarycpython/tests/test_functions.py +++ b/binarycpython/tests/test_functions.py @@ -12,28 +12,24 @@ import h5py from binarycpython.utils.custom_logging_functions import binary_c_log_code from binarycpython.utils.run_system_wrapper import run_system from binarycpython.utils.functions import ( + verbose_print, temp_dir, Capturing, - verbose_print, remove_file, - get_username, - bin_data, create_hdf5, output_lines, - get_defaults, example_parse_output, - create_arg_string, + get_defaults, get_arg_keys, + create_arg_string, get_help, get_help_all, get_help_super, - write_binary_c_parameter_descriptions_to_rst_file, make_build_text, -) -from binarycpython.utils.dicts import AutoVivificationDict, inspect_dict, merge_dicts -from binarycpython.utils.ensemble import ( - binaryc_json_serializer, - handle_ensemble_string_to_json, + write_binary_c_parameter_descriptions_to_rst_file, + + get_username, + bin_data, ) TMP_DIR = temp_dir("tests", "test_functions") @@ -84,39 +80,6 @@ class test_verbose_print(unittest.TestCase): verbose_print("test1", 0, 1) -class test_remove_file(unittest.TestCase): - """ - Unittests for remove_file - """ - - def test_remove_file(self): - with Capturing() as output: - self._test_remove_file() - - def _test_remove_file(self): - """ - Test to remove a file - """ - - with open(os.path.join(TMP_DIR, "test_remove_file_file.txt"), "w") as f: - f.write("test") - - remove_file(os.path.join(TMP_DIR, "test_remove_file_file.txt")) - - def test_remove_nonexisting_file(self): - with Capturing() as output: - self._test_remove_nonexisting_file() - - def _test_remove_nonexisting_file(self): - """ - Test to try to remove a nonexistant file - """ - - file = os.path.join(TMP_DIR, "test_remove_nonexistingfile_file.txt") - - remove_file(file) - - class test_temp_dir(unittest.TestCase): """ Unittests for temp_dir @@ -151,6 +114,39 @@ class test_temp_dir(unittest.TestCase): ) == binary_c_temp_dir +class test_remove_file(unittest.TestCase): + """ + Unittests for remove_file + """ + + def test_remove_file(self): + with Capturing() as output: + self._test_remove_file() + + def _test_remove_file(self): + """ + Test to remove a file + """ + + with open(os.path.join(TMP_DIR, "test_remove_file_file.txt"), "w") as f: + f.write("test") + + remove_file(os.path.join(TMP_DIR, "test_remove_file_file.txt")) + + def test_remove_nonexisting_file(self): + with Capturing() as output: + self._test_remove_nonexisting_file() + + def _test_remove_nonexisting_file(self): + """ + Test to try to remove a nonexistant file + """ + + file = os.path.join(TMP_DIR, "test_remove_nonexistingfile_file.txt") + + remove_file(file) + + class test_create_hdf5(unittest.TestCase): """ Unittests for create_hdf5 @@ -188,89 +184,6 @@ class test_create_hdf5(unittest.TestCase): self.assertIn("settings_2", json.loads(file.get("settings/used_settings")[()])) -# class test_return_binary_c_version_info(unittest.TestCase): -# """ -# Unittests for return_binary_c_version_info -# """ - -# def test_not_parsed(self): -# with Capturing() as output: -# self._test_not_parsed() - -# def _test_not_parsed(self): -# """ -# Test for the raw version_info output -# """ - -# version_info = return_binary_c_version_info(parsed=False) - -# self.assertTrue(isinstance(version_info, str)) -# self.assertIn("Build", version_info) -# self.assertIn("REIMERS_ETA_DEFAULT", version_info) -# self.assertIn("SIGMA_THOMPSON", version_info) - -# def test_parsed(self): -# with Capturing() as output: -# self._test_parsed() - -# def _test_parsed(self): -# """ -# Test for the parssed version_info -# """ - -# # also tests the parse_version_info indirectly -# version_info_parsed = return_binary_c_version_info(parsed=True) - -# self.assertTrue(isinstance(version_info_parsed, dict)) -# self.assertIn("isotopes", version_info_parsed.keys()) -# self.assertIn("argpairs", version_info_parsed.keys()) -# self.assertIn("ensembles", version_info_parsed.keys()) -# self.assertIn("macros", version_info_parsed.keys()) -# self.assertIn("elements", version_info_parsed.keys()) -# self.assertIn("dt_limits", version_info_parsed.keys()) -# self.assertIn("nucleosynthesis_sources", version_info_parsed.keys()) -# self.assertIn("miscellaneous", version_info_parsed.keys()) - - -# class test_parse_binary_c_version_info(unittest.TestCase): -# """ -# Unittests for function parse_binary_c_version_info -# """ - -# def test_1(self): -# with Capturing() as output: -# self._test_1() - -# def _test_1(self): -# """ -# Test for the parsed versio info, more detailed -# """ - -# info = return_binary_c_version_info(parsed=False) -# parsed_info = parse_binary_c_version_info(info) - -# self.assertIn("isotopes", parsed_info.keys()) -# self.assertIn("argpairs", parsed_info.keys()) -# self.assertIn("ensembles", parsed_info.keys()) -# self.assertIn("macros", parsed_info.keys()) -# self.assertIn("elements", parsed_info.keys()) -# self.assertIn("dt_limits", parsed_info.keys()) -# self.assertIn("nucleosynthesis_sources", parsed_info.keys()) -# self.assertIn("miscellaneous", parsed_info.keys()) - -# self.assertIsNotNone(parsed_info["argpairs"]) -# self.assertIsNotNone(parsed_info["ensembles"]) -# self.assertIsNotNone(parsed_info["macros"]) -# self.assertIsNotNone(parsed_info["dt_limits"]) -# self.assertIsNotNone(parsed_info["miscellaneous"]) - -# if parsed_info["macros"]["NUCSYN"] == "on": -# self.assertIsNotNone(parsed_info["isotopes"]) - -# if parsed_info["macros"]["NUCSYN_ID_SOURCES"] == "on": -# self.assertIsNotNone(parsed_info["nucleosynthesis_sources"]) - - class test_output_lines(unittest.TestCase): """ Unittests for function output_lines @@ -647,267 +560,6 @@ class test_write_binary_c_parameter_descriptions_to_rst_file(unittest.TestCase): self.assertTrue(os.path.isfile(output_name)) -class test_inspect_dict(unittest.TestCase): - """ - Unittests for function inspect_dict - """ - - def test_compare_dict(self): - with Capturing() as output: - self._test_compare_dict() - - def _test_compare_dict(self): - """ - Test checking if inspect_dict returns the correct structure by comparing it to known value - """ - - input_dict = { - "int": 1, - "float": 1.2, - "list": [1, 2, 3], - "function": os.path.isfile, - "dict": {"int": 1, "float": 1.2}, - } - output_dict = inspect_dict(input_dict) - compare_dict = { - "int": int, - "float": float, - "list": list, - "function": os.path.isfile.__class__, - "dict": {"int": int, "float": float}, - } - self.assertTrue(compare_dict == output_dict) - - def test_compare_dict_with_print(self): - with Capturing() as output: - self._test_compare_dict_with_print() - - def _test_compare_dict_with_print(self): - """ - Test checking output is printed - """ - - input_dict = { - "int": 1, - "float": 1.2, - "list": [1, 2, 3], - "function": os.path.isfile, - "dict": {"int": 1, "float": 1.2}, - } - output_dict = inspect_dict(input_dict, print_structure=True) - - -class test_merge_dicts(unittest.TestCase): - """ - Unittests for function merge_dicts - """ - - def test_empty(self): - with Capturing() as output: - self._test_empty() - - def _test_empty(self): - """ - Test merging an empty dict - """ - - input_dict = { - "int": 1, - "float": 1.2, - "list": [1, 2, 3], - "function": os.path.isfile, - "dict": {"int": 1, "float": 1.2}, - } - dict_2 = {} - output_dict = merge_dicts(input_dict, dict_2) - self.assertTrue(output_dict == input_dict) - - def test_unequal_types(self): - with Capturing() as output: - self._test_unequal_types() - - def _test_unequal_types(self): - """ - Test merging unequal types: should raise valueError - """ - - dict_1 = {"input": 10} - dict_2 = {"input": "hello"} - - self.assertRaises(ValueError, merge_dicts, dict_1, dict_2) - - def test_bools(self): - with Capturing() as output: - self._test_bools() - - def _test_bools(self): - """ - Test merging dict with booleans - """ - - dict_1 = {"bool": True} - dict_2 = {"bool": False} - output_dict = merge_dicts(dict_1, dict_2) - - self.assertTrue(isinstance(output_dict["bool"], bool)) - self.assertTrue(output_dict["bool"]) - - def test_ints(self): - with Capturing() as _: - self._test_ints() - - def _test_ints(self): - """ - Test merging dict with ints - """ - - dict_1 = {"int": 2} - dict_2 = {"int": 1} - output_dict = merge_dicts(dict_1, dict_2) - - self.assertTrue(isinstance(output_dict["int"], int)) - self.assertEqual(output_dict["int"], 3) - - def test_floats(self): - with Capturing() as output: - self._test_floats() - - def _test_floats(self): - """ - Test merging dict with floats - """ - - dict_1 = {"float": 4.5} - dict_2 = {"float": 4.6} - output_dict = merge_dicts(dict_1, dict_2) - - self.assertTrue(isinstance(output_dict["float"], float)) - self.assertEqual(output_dict["float"], 9.1) - - def test_lists(self): - with Capturing() as output: - self._test_lists() - - def _test_lists(self): - """ - Test merging dict with lists - """ - - dict_1 = {"list": [1, 2]} - dict_2 = {"list": [3, 4]} - output_dict = merge_dicts(dict_1, dict_2) - - self.assertTrue(isinstance(output_dict["list"], list)) - self.assertEqual(output_dict["list"], [1, 2, 3, 4]) - - def test_dicts(self): - with Capturing() as _: - self._test_dicts() - - def _test_dicts(self): - """ - Test merging dict with dicts - """ - - dict_1 = {"dict": {"same": 1, "other_1": 2.0}} - dict_2 = {"dict": {"same": 2, "other_2": [4.0]}} - output_dict = merge_dicts(dict_1, dict_2) - - self.assertTrue(isinstance(output_dict["dict"], dict)) - self.assertEqual( - output_dict["dict"], {"same": 3, "other_1": 2.0, "other_2": [4.0]} - ) - - def test_unsupported(self): - with Capturing() as output: - self._test_unsupported() - - def _test_unsupported(self): - """ - Test merging dict with unsupported types. should raise ValueError - """ - - dict_1 = {"new": dummy("david")} - dict_2 = {"new": dummy("gio")} - - # output_dict = merge_dicts(dict_1, dict_2) - self.assertRaises(ValueError, merge_dicts, dict_1, dict_2) - - -class test_binaryc_json_serializer(unittest.TestCase): - """ - Unittests for function binaryc_json_serializer - """ - - def test_not_function(self): - with Capturing() as output: - self._test_not_function() - - def _test_not_function(self): - """ - Test passing an object that doesnt get turned in to a string - """ - - stringo = "hello" - output = binaryc_json_serializer(stringo) - self.assertTrue(stringo == output) - - def test_function(self): - with Capturing() as output: - self._test_function() - - def _test_function(self): - """ - Test passing an object that gets turned in to a string: a function - """ - - string_of_function = str(os.path.isfile) - output = binaryc_json_serializer(os.path.isfile) - self.assertTrue(string_of_function == output) - - -class test_handle_ensemble_string_to_json(unittest.TestCase): - """ - Unittests for function handle_ensemble_string_to_json - """ - - def test_1(self): - with Capturing() as _: - self._test_1() - - def _test_1(self): - """ - Test passing string representation of a dictionary. - """ - - _ = str(os.path.isfile) - input_string = '{"ding": 10, "list_example": [1,2,3]}' - output_dict = handle_ensemble_string_to_json(input_string) - - self.assertTrue(isinstance(output_dict, dict)) - self.assertTrue(output_dict["ding"] == 10) - self.assertTrue(output_dict["list_example"] == [1, 2, 3]) - - -class test_AutoVivicationDict(unittest.TestCase): - """ - Unittests for AutoVivicationDict - """ - - def test_add(self): - """ - Tests to see if the adding is done correctly - """ - - result_dict = AutoVivificationDict() - - result_dict["a"]["b"]["c"] += 10 - - self.assertEqual(result_dict["a"]["b"]["c"], 10) - result_dict["a"]["b"]["c"] += 10 - self.assertEqual(result_dict["a"]["b"]["c"], 20) - - class test_bin_data(unittest.TestCase): """ Unittests for bin_data diff --git a/binarycpython/tests/tmp_functions.py b/binarycpython/tests/tmp_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..a5dcbeecb9f480d894eef34ab2e7326a4c5e1910 --- /dev/null +++ b/binarycpython/tests/tmp_functions.py @@ -0,0 +1,353 @@ +from binarycpython.utils.dicts import AutoVivificationDict, inspect_dict, merge_dicts +from binarycpython.utils.ensemble import ( + binaryc_json_serializer, + handle_ensemble_string_to_json, +) + + +# class test_return_binary_c_version_info(unittest.TestCase): +# """ +# Unittests for return_binary_c_version_info +# """ + +# def test_not_parsed(self): +# with Capturing() as output: +# self._test_not_parsed() + +# def _test_not_parsed(self): +# """ +# Test for the raw version_info output +# """ + +# version_info = return_binary_c_version_info(parsed=False) + +# self.assertTrue(isinstance(version_info, str)) +# self.assertIn("Build", version_info) +# self.assertIn("REIMERS_ETA_DEFAULT", version_info) +# self.assertIn("SIGMA_THOMPSON", version_info) + +# def test_parsed(self): +# with Capturing() as output: +# self._test_parsed() + +# def _test_parsed(self): +# """ +# Test for the parssed version_info +# """ + +# # also tests the parse_version_info indirectly +# version_info_parsed = return_binary_c_version_info(parsed=True) + +# self.assertTrue(isinstance(version_info_parsed, dict)) +# self.assertIn("isotopes", version_info_parsed.keys()) +# self.assertIn("argpairs", version_info_parsed.keys()) +# self.assertIn("ensembles", version_info_parsed.keys()) +# self.assertIn("macros", version_info_parsed.keys()) +# self.assertIn("elements", version_info_parsed.keys()) +# self.assertIn("dt_limits", version_info_parsed.keys()) +# self.assertIn("nucleosynthesis_sources", version_info_parsed.keys()) +# self.assertIn("miscellaneous", version_info_parsed.keys()) + + + +# class test_parse_binary_c_version_info(unittest.TestCase): +# """ +# Unittests for function parse_binary_c_version_info +# """ + +# def test_1(self): +# with Capturing() as output: +# self._test_1() + +# def _test_1(self): +# """ +# Test for the parsed versio info, more detailed +# """ + +# info = return_binary_c_version_info(parsed=False) +# parsed_info = parse_binary_c_version_info(info) + +# self.assertIn("isotopes", parsed_info.keys()) +# self.assertIn("argpairs", parsed_info.keys()) +# self.assertIn("ensembles", parsed_info.keys()) +# self.assertIn("macros", parsed_info.keys()) +# self.assertIn("elements", parsed_info.keys()) +# self.assertIn("dt_limits", parsed_info.keys()) +# self.assertIn("nucleosynthesis_sources", parsed_info.keys()) +# self.assertIn("miscellaneous", parsed_info.keys()) + +# self.assertIsNotNone(parsed_info["argpairs"]) +# self.assertIsNotNone(parsed_info["ensembles"]) +# self.assertIsNotNone(parsed_info["macros"]) +# self.assertIsNotNone(parsed_info["dt_limits"]) +# self.assertIsNotNone(parsed_info["miscellaneous"]) + +# if parsed_info["macros"]["NUCSYN"] == "on": +# self.assertIsNotNone(parsed_info["isotopes"]) + +# if parsed_info["macros"]["NUCSYN_ID_SOURCES"] == "on": +# self.assertIsNotNone(parsed_info["nucleosynthesis_sources"]) + + + +class test_inspect_dict(unittest.TestCase): + """ + Unittests for function inspect_dict + """ + + def test_compare_dict(self): + with Capturing() as output: + self._test_compare_dict() + + def _test_compare_dict(self): + """ + Test checking if inspect_dict returns the correct structure by comparing it to known value + """ + + input_dict = { + "int": 1, + "float": 1.2, + "list": [1, 2, 3], + "function": os.path.isfile, + "dict": {"int": 1, "float": 1.2}, + } + output_dict = inspect_dict(input_dict) + compare_dict = { + "int": int, + "float": float, + "list": list, + "function": os.path.isfile.__class__, + "dict": {"int": int, "float": float}, + } + self.assertTrue(compare_dict == output_dict) + + def test_compare_dict_with_print(self): + with Capturing() as output: + self._test_compare_dict_with_print() + + def _test_compare_dict_with_print(self): + """ + Test checking output is printed + """ + + input_dict = { + "int": 1, + "float": 1.2, + "list": [1, 2, 3], + "function": os.path.isfile, + "dict": {"int": 1, "float": 1.2}, + } + output_dict = inspect_dict(input_dict, print_structure=True) + + +class test_merge_dicts(unittest.TestCase): + """ + Unittests for function merge_dicts + """ + + def test_empty(self): + with Capturing() as output: + self._test_empty() + + def _test_empty(self): + """ + Test merging an empty dict + """ + + input_dict = { + "int": 1, + "float": 1.2, + "list": [1, 2, 3], + "function": os.path.isfile, + "dict": {"int": 1, "float": 1.2}, + } + dict_2 = {} + output_dict = merge_dicts(input_dict, dict_2) + self.assertTrue(output_dict == input_dict) + + def test_unequal_types(self): + with Capturing() as output: + self._test_unequal_types() + + def _test_unequal_types(self): + """ + Test merging unequal types: should raise valueError + """ + + dict_1 = {"input": 10} + dict_2 = {"input": "hello"} + + self.assertRaises(ValueError, merge_dicts, dict_1, dict_2) + + def test_bools(self): + with Capturing() as output: + self._test_bools() + + def _test_bools(self): + """ + Test merging dict with booleans + """ + + dict_1 = {"bool": True} + dict_2 = {"bool": False} + output_dict = merge_dicts(dict_1, dict_2) + + self.assertTrue(isinstance(output_dict["bool"], bool)) + self.assertTrue(output_dict["bool"]) + + def test_ints(self): + with Capturing() as _: + self._test_ints() + + def _test_ints(self): + """ + Test merging dict with ints + """ + + dict_1 = {"int": 2} + dict_2 = {"int": 1} + output_dict = merge_dicts(dict_1, dict_2) + + self.assertTrue(isinstance(output_dict["int"], int)) + self.assertEqual(output_dict["int"], 3) + + def test_floats(self): + with Capturing() as output: + self._test_floats() + + def _test_floats(self): + """ + Test merging dict with floats + """ + + dict_1 = {"float": 4.5} + dict_2 = {"float": 4.6} + output_dict = merge_dicts(dict_1, dict_2) + + self.assertTrue(isinstance(output_dict["float"], float)) + self.assertEqual(output_dict["float"], 9.1) + + def test_lists(self): + with Capturing() as output: + self._test_lists() + + def _test_lists(self): + """ + Test merging dict with lists + """ + + dict_1 = {"list": [1, 2]} + dict_2 = {"list": [3, 4]} + output_dict = merge_dicts(dict_1, dict_2) + + self.assertTrue(isinstance(output_dict["list"], list)) + self.assertEqual(output_dict["list"], [1, 2, 3, 4]) + + def test_dicts(self): + with Capturing() as _: + self._test_dicts() + + def _test_dicts(self): + """ + Test merging dict with dicts + """ + + dict_1 = {"dict": {"same": 1, "other_1": 2.0}} + dict_2 = {"dict": {"same": 2, "other_2": [4.0]}} + output_dict = merge_dicts(dict_1, dict_2) + + self.assertTrue(isinstance(output_dict["dict"], dict)) + self.assertEqual( + output_dict["dict"], {"same": 3, "other_1": 2.0, "other_2": [4.0]} + ) + + def test_unsupported(self): + with Capturing() as output: + self._test_unsupported() + + def _test_unsupported(self): + """ + Test merging dict with unsupported types. should raise ValueError + """ + + dict_1 = {"new": dummy("david")} + dict_2 = {"new": dummy("gio")} + + # output_dict = merge_dicts(dict_1, dict_2) + self.assertRaises(ValueError, merge_dicts, dict_1, dict_2) + + +class test_binaryc_json_serializer(unittest.TestCase): + """ + Unittests for function binaryc_json_serializer + """ + + def test_not_function(self): + with Capturing() as output: + self._test_not_function() + + def _test_not_function(self): + """ + Test passing an object that doesnt get turned in to a string + """ + + stringo = "hello" + output = binaryc_json_serializer(stringo) + self.assertTrue(stringo == output) + + def test_function(self): + with Capturing() as output: + self._test_function() + + def _test_function(self): + """ + Test passing an object that gets turned in to a string: a function + """ + + string_of_function = str(os.path.isfile) + output = binaryc_json_serializer(os.path.isfile) + self.assertTrue(string_of_function == output) + + +class test_handle_ensemble_string_to_json(unittest.TestCase): + """ + Unittests for function handle_ensemble_string_to_json + """ + + def test_1(self): + with Capturing() as _: + self._test_1() + + def _test_1(self): + """ + Test passing string representation of a dictionary. + """ + + _ = str(os.path.isfile) + input_string = '{"ding": 10, "list_example": [1,2,3]}' + output_dict = handle_ensemble_string_to_json(input_string) + + self.assertTrue(isinstance(output_dict, dict)) + self.assertTrue(output_dict["ding"] == 10) + self.assertTrue(output_dict["list_example"] == [1, 2, 3]) + + +class test_AutoVivicationDict(unittest.TestCase): + """ + Unittests for AutoVivicationDict + """ + + def test_add(self): + """ + Tests to see if the adding is done correctly + """ + + result_dict = AutoVivificationDict() + + result_dict["a"]["b"]["c"] += 10 + + self.assertEqual(result_dict["a"]["b"]["c"], 10) + result_dict["a"]["b"]["c"] += 10 + self.assertEqual(result_dict["a"]["b"]["c"], 20) + +