"""Test suite for functions defined in sohstationviewer.controller.util.""" import os from unittest import TestCase from unittest.mock import patch from tempfile import TemporaryDirectory, NamedTemporaryFile import string from sohstationviewer.controller.util import ( validateFile, getDirSize, getTime6, getTime6_2y, getTime6_4y, getTime4, getVal, rtnPattern, fmti ) TEST_DATA_DIR = os.path.realpath(os.path.join( os.path.dirname(os.path.realpath(__file__)), os.pardir, 'test_data', )) class TestGetTime(TestCase): """Test suite for getTime6, getTime6_2y, getTime6_4y, and getTime4.""" def setUp(self): """Set up text fixtures.""" self.time6_2y = '01:251:09:41:35:656' self.time6_4y = '2001:251:09:41:35:656' self.time4_day_1 = '1:09:41:35' self.time4 = '251:09:41:35' @patch('sohstationviewer.controller.util.getTime6_4y') @patch('sohstationviewer.controller.util.getTime6_2y') def test_get_time6(self, mock_2y, mock_4y): """ Test getTime6 - check that getTime6 delegates work to the appropriate helper function depending on the input. """ with self.subTest('test_2_digit_year'): getTime6(self.time6_2y) self.assertTrue(mock_2y.called) self.assertFalse(mock_4y.called) mock_2y.reset_mock() mock_4y.reset_mock() with self.subTest('test_4_digit_year'): getTime6(self.time6_4y) self.assertTrue(mock_4y.called) self.assertFalse(mock_2y.called) def test_get_time6_invalid_input(self): """Test getTime6 - the input is not one of the expected formats.""" with self.subTest('test_input_contains_colon'): bad_inputs = [':523:531:', 'fs:523:531:', 'towe:523:531:'] for input_str in bad_inputs: with self.assertRaises(ValueError): getTime6(input_str) with self.subTest('test_input_does_not_contain_colon'): input_str = 'fq31dqrt63' with self.assertRaises(ValueError): getTime6(input_str) def test_get_time6_2y(self): """Test getTime6_2y.""" epoch_time, year = getTime6_2y(self.time6_2y) self.assertAlmostEqual(epoch_time, 999942095.656) self.assertEqual(year, 2001) def test_get_time6_4y(self): """Test getTime6_4y.""" epoch_time, year = getTime6_4y(self.time6_4y) self.assertAlmostEqual(epoch_time, 999942095.656) self.assertEqual(year, 2001) def test_get_time4_year_added(self): """Test getTime4 - a year has been added.""" year = 2001 year_added = True with self.subTest('test_first_day_of_year'): epoch_time, ret_year, ret_year_added = ( getTime4(self.time4_day_1, year, year_added) ) self.assertEqual(epoch_time, 978342095) self.assertEqual(ret_year, year) self.assertTrue(ret_year_added) with self.subTest('test_other_days'): epoch_time, ret_year, ret_year_added = ( getTime4(self.time4, year, year_added) ) self.assertEqual(epoch_time, 999942095) self.assertEqual(ret_year, year) self.assertTrue(ret_year_added) def test_get_time4_year_not_added(self): """Test getTime4 - a year has not been added.""" year = 2001 year_added = False with self.subTest('test_first_day_of_year'): epoch_time, ret_year, ret_year_added = ( getTime4(self.time4_day_1, year, year_added) ) self.assertEqual(epoch_time, 1009878095) self.assertEqual(ret_year, year + 1) self.assertTrue(ret_year_added) with self.subTest('test_other_days'): epoch_time, ret_year, ret_year_added = ( getTime4(self.time4, year, year_added) ) self.assertEqual(epoch_time, 999942095) self.assertEqual(ret_year, year) self.assertFalse(ret_year_added) class TestValidateFile(TestCase): """Test suite for validateFile.""" def test_valid_file(self): """ Test basic functionality of validateFile - given file exists and is not an info file. """ with NamedTemporaryFile() as valid_file: self.assertTrue( validateFile(valid_file.name, os.path.basename(valid_file.name)) ) def test_info_file(self): """ Test basic functionality of validateFile - given file exists and is an info file. """ with self.subTest('test_dot_DS_Store'): with TemporaryDirectory() as temp_dir: # There are two ways to create a temporary file with a # custom name. The first way is to create a temporary # directory, then create a file in that directory. When the # temporary directory is deleted, the file is also deleted. # This automatic deletion effectively makes the file we created # a temporary file. # The second way is to create a temporary file and rename it # to the name we want. Then, we need to rename the file back # to its original name once we are done. This renaming is # needed because temporary file deletes itself based on its # name attribute, which is not changed in the first # renaming. While the second way makes it explicit that we # only want a temporary file, it might not be clear why the # second renaming is needed. In order to cause less # confusion in the code, we create this temporary file the # first way. ds_store_path = os.path.join(temp_dir, '.DS_Store') with open(ds_store_path, 'w+') as ds_store_file: self.assertFalse( validateFile(ds_store_path, os.path.basename(ds_store_file.name)) ) with self.subTest('test_dot_underscore'): with NamedTemporaryFile(prefix='._') as info_file: self.assertFalse( validateFile(info_file.name, os.path.basename(info_file.name)) ) def test_file_does_not_exist(self): """ Test basic functionality of validateFile - given file does not exist. """ empty_name_file = '' self.assertFalse(validateFile(empty_name_file, empty_name_file)) not_exist_file = 'file_does_not_exist' self.assertFalse(validateFile(not_exist_file, not_exist_file)) class TestGetDirSize(TestCase): """Test suite for getDirSize.""" def test_files_have_size_zero(self): """Test getDirSize - all files in the given directory has size zero.""" expected_file_count = 10 with TemporaryDirectory() as directory: files = [] for i in range(expected_file_count): files.append(NamedTemporaryFile(dir=directory)) dir_size, dir_file_count = getDirSize(directory) self.assertEqual(dir_size, 0) self.assertEqual(dir_file_count, expected_file_count) # Explicitly clean up the temporary files. If we don't do this, # the temporary directory will clean up itself and delete the # temporary files. Then, when the function returns, the references # to these temporary files will attempt to clean up the files. This # leads to exceptions being raised because the files being cleaned # up does not exist any more. files = [file.close() for file in files] def test_files_have_size_greater_than_zero(self): """ Test getDirSize - all files in the given directory have a size greater than zero. """ expected_file_count = 10 size_one_file = 10 # Each byte character is 1 byte, so we can obtain a byte string of n # bytes by creating a byte string with n characters. byte_to_write = b'a' * size_one_file temp_dir = TemporaryDirectory() with TemporaryDirectory(dir=temp_dir.name) as directory: files = [] for i in range(expected_file_count): temp_file = NamedTemporaryFile(dir=directory) temp_file.write(byte_to_write) # Writing to file is not done until requested so we need to # explicitly tell Python to finish the write. Otherwise, the # size of the file on disk will stay 0. temp_file.flush() files.append(temp_file) dir_size, dir_file_count = getDirSize(temp_dir.name) self.assertEqual(dir_size, size_one_file * expected_file_count) self.assertEqual(dir_file_count, expected_file_count) # Explicitly clean up the temporary files. If we don't do this, # the temporary directory will clean up itself and delete the # temporary files. Then, when the function returns, the references # to these temporary files will attempt to clean up the files. This # leads to exceptions being raised because the files being cleaned # up does not exist any more. files = [file.close() for file in files] def test_nested_folder_structure(self): """ Test getDirSize - the given directory contains nested directories. """ test_folder = os.path.join(TEST_DATA_DIR, 'Pegasus-sample') dir_size, dir_file_count = getDirSize(test_folder) self.assertEqual(dir_size, 7909115) self.assertEqual(dir_file_count, 8) def test_empty_directory(self): """Test getDirSize - the given directory contains no file.""" with TemporaryDirectory() as temp_dir: dir_size, dir_file_count = getDirSize(temp_dir) self.assertEqual(dir_size, 0) self.assertEqual(dir_file_count, 0) def test_directory_does_not_exist(self): """Test getDirSize - the given directory does not exist.""" empty_name_dir = '' dir_size, dir_file_count = getDirSize(empty_name_dir) self.assertEqual(dir_size, 0) self.assertEqual(dir_file_count, 0) non_existent_dir = 'directory does not exist' dir_size, dir_file_count = getDirSize(non_existent_dir) self.assertEqual(dir_size, 0) self.assertEqual(dir_file_count, 0) class TestRtnPattern(TestCase): """Test suite for rtnPattern.""" def test_no_upper(self): """Test rtnPattern - characters are not converted to uppercase.""" with self.subTest('test_digit'): digits = '123456789' self.assertEqual(rtnPattern(digits), '0' * len(digits)) with self.subTest('test_lowercase'): lowercase_chars = string.ascii_lowercase self.assertEqual(rtnPattern(lowercase_chars), 'a' * len(lowercase_chars)) with self.subTest('test_uppercase'): uppercase_chars = string.ascii_uppercase self.assertEqual(rtnPattern(uppercase_chars), 'A' * len(uppercase_chars)) def test_with_upper(self): """Test rtnPattern - all characters are converted to uppercase.""" lowercase_chars = string.ascii_lowercase self.assertEqual(rtnPattern(lowercase_chars, upper=True), 'A' * len(lowercase_chars)) class TestGetVal(TestCase): """Test suite for getVal.""" def test_normal_case(self): """Test getVal - the input is of an expected value.""" # formatter:off test_name_to_test_map = { 'test_with_decimal_point': ('60.3V', 60.3), 'test_no_decimal_point': ('15V', 15.0), 'test_with_plus_sign': ('+35.5V', 35.5), 'test_with_negative_sign': ('-35.2V', -35.2), 'test_multiple_decimal_digits': ('52.523V', 52.5), 'test_no_decimal_digit': ('12.V', 12.0), } # formatter:on for test_name, inout_pair in test_name_to_test_map.items(): with self.subTest(test_name): self.assertEqual(getVal(inout_pair[0]), inout_pair[1]) def test_positive_negative_sign_in_front(self): """ Test rtnPattern - the input has both a positive sign and a negative sign in the front. """ with self.assertRaises(ValueError): getVal('+-1.0V') def test_bad_input(self): """Test rtnPattern - the input has a value that is not expected.""" with self.assertRaises(AttributeError): getVal('') class TestFmti(TestCase): """Test suite for fmti.""" def test_absolute_value_below_1000(self): """ Test fmti - the input is greater than -1000 but smaller than 1000. """ with self.subTest('test_positive'): val = 52.521 self.assertEqual(fmti(val), '52') with self.subTest('test_negative'): val = -232.42 self.assertEqual(fmti(val), '-232') def test_absolute_value_above_1000(self): """ Test fmti - the input is greater than 1000 or smaller than -1000. """ with self.subTest('test_positive'): val = 136235646.215151 expected = '136,235,646' self.assertEqual(fmti(val), expected) with self.subTest('test_negative'): val = -62362.32523 expected = '-62,362' self.assertEqual(fmti(val), expected)