Skip to content
Snippets Groups Projects
test_util.py 13.6 KiB
Newer Older
"""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)