Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add unit testing
Browse files Browse the repository at this point in the history
initial coverage and unittest additions

initial commit for test_utils.py

initial commit for test_msg.py & .coverage

add test_LLI.py & update coverage

add intentionally-failing test backup file

require successful tests before building the binary
n8marti committed Apr 3, 2024
1 parent e9ac19d commit da1e28d
Showing 15 changed files with 449 additions and 8 deletions.
Binary file added .coverage
Binary file not shown.
1 change: 1 addition & 0 deletions .github/workflows/build-branch.yml
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ jobs:
# apt-get install python3-tk
pip install --upgrade pip
pip install -r requirements.txt
pip install coverage
pip install pyinstaller
- name: Build with pyinstaller
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -92,12 +92,12 @@ or the script provided in `scripts/ensure-python.sh`. This is because the app is
built using 3.12 and might have errors if run with other versions.
```
# install build dependencies; e.g. for debian-based systems:
$ apt install build-essential tcl-dev tk-dev libreadline-dev
$ apt install build-essential tcl-dev tk-dev libreadline-dev libsqlite3-dev
# install & build python 3.12
$ wget 'https://www.python.org/ftp/python/3.12.1/Python-3.12.1.tar.xz'
$ tar xf Python-3.12.1.tar.xz
$ cd Python-3.12.1
Python-3.12.1$ ./configure --prefix=/opt --enable-shared
Python-3.12.1$ ./configure --prefix=/opt --enable-shared --enable-loadable-sqlite-extensions
Python-3.12.1$ make
Python-3.12.1$ sudo make install
Python-3.12.1$ LD_LIBRARY_PATH=/opt/lib /opt/bin/python3.12 --version
15 changes: 12 additions & 3 deletions scripts/build-binary.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
#!/usr/bin/env bash
script_dir="$(dirname "$0")"
repo_root="$(dirname "$script_dir")"
if ! which pyinstaller >/dev/null 2>&1; then
echo "Error: Need to install pyinstaller; e.g. 'pip3 install pyinstaller'"
if ! python -c 'import coverage' >/dev/null 2>&1; then
echo "Error: Need to install coverage; e.g. 'pip install coverage'"
exit 1
fi
python3 -m PyInstaller --clean "${repo_root}/LogosLinuxInstaller.spec"
if ! python -c 'import PyInstaller' >/dev/null 2>&1; then
echo "Error: Need to install pyinstaller; e.g. 'pip install pyinstaller'"
exit 1
fi
if ! python -m coverage run -m unittest -b; then
echo "Error: Must past unittests before building"
echo "Run 'python -m coverage run -m unittest -b -v' to see which test is failing"
exit 1
fi
python -m PyInstaller --clean "${repo_root}/LogosLinuxInstaller.spec"
9 changes: 6 additions & 3 deletions scripts/ensure-python.sh
Original file line number Diff line number Diff line change
@@ -18,8 +18,8 @@ fi

# Warn about build deps.
echo "Warning: You will likely need to install build dependencies for your system."
echo "e.g. Ubuntu requires: build-essential libreadline-dev tk-dev tcl-dev"
read -pr "Continue? [Y/n] " ans
echo "e.g. Ubuntu requires: build-essential libreadline-dev libsqlite3-dev tk-dev tcl-dev"
read -rp "Continue? [y/N]: " ans
if [[ ${ans,,} != 'y' ]]; then
exit 1
fi
@@ -44,7 +44,10 @@ fi

# Install python.
echo "Installing..."
./configure --enable-shared --prefix="$prefix"
./configure \
--enable-shared \
--enable-loadable-sqlite-extensions \
--prefix="$prefix"
make
sudo make install

Empty file added test/__init__.py
Empty file.
1 change: 1 addition & 0 deletions test/data/config.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TARGETVERSION=10
30 changes: 30 additions & 0 deletions test/data/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"APPDIR": null,
"APPDIR_BINDIR": null,
"APPIMAGE_FILENAME": null,
"BACKUPDIR": null,
"FLPRODUCT": null,
"FLPRODUCTi": null,
"INSTALLDIR": null,
"LAST_UPDATED": "2024-04-01T12:23:50",
"LOGOS_DIR": null,
"LOGOS_EXE": null,
"LOGOS_EXECUTABLE": null,
"LOGOS_LATEST_VERSION": null,
"LOGOS_LATEST_VERSION_FILENAME": "LogosLinuxInstaller",
"LOGOS_LATEST_VERSION_URL": "https://github.com/FaithLife-Community/LogosLinuxInstaller/releases/download/v4.0.0-alpha.3/LogosLinuxInstaller",
"LOGS": null,
"RECOMMENDED_WINE64_APPIMAGE_BRANCH": "devel",
"RECOMMENDED_WINE64_APPIMAGE_FULL_FILENAME": "wine-devel_8.19-x86_64.AppImage",
"RECOMMENDED_WINE64_APPIMAGE_FULL_URL": "https://github.com/FaithLife-Community/wine-appimages/releases/download/8.19-devel/wine-devel_8.19-x86_64.AppImage",
"RECOMMENDED_WINE64_APPIMAGE_FULL_VERSION": "v8.19-devel",
"RECOMMENDED_WINE64_APPIMAGE_VERSION": "8.19",
"SELECTED_APPIMAGE_FILENAME": null,
"TARGETVERSION": "10",
"WINEBIN_CODE": null,
"WINECMD_ENCODING": null,
"WINEPREFIX": null,
"WINESERVER_EXE": null,
"WINETRICKSBIN": null,
"WINE_EXE": null
}
1 change: 1 addition & 0 deletions test/data/config_bad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{
2 changes: 2 additions & 0 deletions test/data/config_empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
137 changes: 137 additions & 0 deletions test/test_LLI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import argparse
import unittest
from pathlib import Path
from unittest.mock import patch

import config
import LogosLinuxInstaller as LLI


class TestLLICli(unittest.TestCase):
def setUp(self):
config.CONFIG_FILE = Path(__file__).parent / 'data' / 'config.json'
patcher = patch(
'argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(
skip_fonts=None,
check_for_updates=None,
skip_dependencies=None,
verbose=None,
debug=None,
config=None,
force_root=None,
custom_binary_path=None,
delete_log=None,
passive=None,
install_app=None,
run_installed_app=None,
run_indexing=None,
remove_library_catalog=None,
remove_index_files=None,
edit_config=None,
install_dependencies=None,
backup=None,
restore=None,
update_self=None,
update_latest_appimage=None,
set_appimage=None,
get_winetricks=None,
run_winetricks=None,
toggle_app_logging=None,
create_shortcuts=None,
remove_install_dir=None,
dirlink=None,
make_skel=None,
check_resources=None,
)
)
self.parser = LLI.get_parser()
self.mock_parse_args = patcher.start()
self.addCleanup(patcher.stop)

def test_parse_args_backup(self):
self.mock_parse_args.return_value.backup = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('backup', config.ACTION.__name__)

def test_parse_args_create_shortcuts(self):
self.mock_parse_args.return_value.create_shortcuts = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('create_shortcuts', config.ACTION.__name__)

def test_parse_args_edit_config(self):
self.mock_parse_args.return_value.edit_config = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('edit_config', config.ACTION.__name__)

def test_parse_args_install_app(self):
self.mock_parse_args.return_value.install_app = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('install', config.ACTION.__name__)

def test_parse_args_install_dependencies(self):
self.mock_parse_args.return_value.install_dependencies = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('check_dependencies', config.ACTION.__name__)

def test_parse_args_remove_index_files(self):
self.mock_parse_args.return_value.remove_index_files = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('remove_all_index_files', config.ACTION.__name__)

def test_parse_args_remove_install_dir(self):
self.mock_parse_args.return_value.remove_install_dir = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('remove_install_dir', config.ACTION.__name__)

def test_parse_args_remove_library_catalog(self):
self.mock_parse_args.return_value.remove_library_catalog = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('remove_library_catalog', config.ACTION.__name__)

def test_parse_args_restore(self):
self.mock_parse_args.return_value.restore = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('restore', config.ACTION.__name__)

def test_parse_args_run_indexing(self):
self.mock_parse_args.return_value.run_indexing = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('run_indexing', config.ACTION.__name__)

def test_parse_args_run_installed_app(self):
self.mock_parse_args.return_value.run_installed_app = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('run_logos', config.ACTION.__name__)

def test_parse_args_run_winetricks(self):
self.mock_parse_args.return_value.run_winetricks = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('run_winetricks', config.ACTION.__name__)

def test_parse_args_toggle_app_logging(self):
self.mock_parse_args.return_value.toggle_app_logging = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('switch_logging', config.ACTION.__name__)

def test_parse_args_update_latest_appimage_disabled(self):
self.mock_parse_args.return_value.update_latest_appimage = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual('disabled', config.ACTION)

def test_parse_args_update_latest_appimage_enabled(self):
config.WINEBIN_CODE = 'AppImage'
self.mock_parse_args.return_value.update_latest_appimage = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual(
'update_to_latest_recommended_appimage',
config.ACTION.__name__
)

def test_parse_args_update_self(self):
self.mock_parse_args.return_value.update_self = True
LLI.parse_args(self.mock_parse_args.return_value, self.parser)
self.assertEqual(
'update_to_latest_lli_release',
config.ACTION.__name__
)
41 changes: 41 additions & 0 deletions test/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
import os
import unittest

import config


class TestConfig(unittest.TestCase):
def test_get_config_file_dict_badjson(self):
config.get_config_file_dict('test/data/config_bad.json')
self.assertRaises(json.JSONDecodeError)

def test_get_config_file_dict_empty(self):
config_dict = config.get_config_file_dict('test/data/config_empty.json') # noqa: E501
self.assertEqual(0, len(config_dict))

def test_get_config_file_dict_json(self):
config_dict = config.get_config_file_dict('test/data/config.json')
self.assertEqual(config_dict.get('TARGETVERSION'), '10')

def test_get_config_file_dict_none(self):
config.get_config_file_dict('test/data/not_there.json')
self.assertRaises(FileNotFoundError)

def test_get_config_file_dict_conf(self):
config_dict = config.get_config_file_dict('test/data/config.conf')
self.assertEqual(config_dict.get('TARGETVERSION'), '10')

def test_get_env_config(self):
config.FLPRODUCT = None
os.environ['FLPRODUCT'] = 'Logos'
config.get_env_config()
self.assertEqual(os.getenv('FLPRODUCT'), config.FLPRODUCT)

def test_set_config_env_bad(self):
self.assertIsNone(config.set_config_env('test/data/config_empty.json'))

def test_set_env_config_value(self):
config.TARGETVERSION = None
config.set_config_env('test/data/config.json')
self.assertEqual(config.TARGETVERSION, '10')
6 changes: 6 additions & 0 deletions test/test_fail.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import unittest


class TestFake(unittest.TestCase):
def test_force_failure(self):
self.assertEqual(False, True)
95 changes: 95 additions & 0 deletions test/test_msg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import unittest
from unittest.mock import patch

import config
import logging
import msg


class TestMsg(unittest.TestCase):
def test_get_log_level_name(self):
name = msg.get_log_level_name(logging.DEBUG)
self.assertEqual(name, 'DEBUG')

def test_update_log_level(self):
new_level = logging.DEBUG
level = None
msg.update_log_level(new_level)
for h in logging.getLogger().handlers:
if isinstance(h, logging.StreamHandler):
level = h.level
self.assertEqual(level, new_level)

@patch('msg.input', create=True)
def test_cli_acknowledge_question_empty(self, mocked_input):
mocked_input.side_effect = ['']
result = msg.cli_acknowledge_question('test', 'no')
self.assertTrue(result)

@patch('msg.input', create=True)
def test_cli_acknowledge_question_no(self, mocked_input):
mocked_input.side_effect = ['N']
result = msg.cli_acknowledge_question('test', 'no')
self.assertFalse(result)

@patch('msg.input', create=True)
def test_cli_acknowledge_question_yes(self, mocked_input):
mocked_input.side_effect = ['Y']
result = msg.cli_acknowledge_question('test', 'no')
self.assertTrue(result)

@patch('msg.input', create=True)
def test_cli_ask_filepath(self, mocked_input):
path = "/home/user/Directory"
mocked_input.side_effect = [f"\"{path}\""]
result = msg.cli_ask_filepath('test')
self.assertEqual(path, result)

@patch('msg.input', create=True)
def test_cli_continue_question_yes(self, mocked_input):
mocked_input.side_effect = ['Y']
result = msg.cli_continue_question('test', 'no', None)
self.assertIsNone(result)

@patch('msg.input', create=True)
def test_cli_question_empty(self, mocked_input):
mocked_input.side_effect = ['']
self.assertTrue(msg.cli_question('test'))

@patch('msg.input', create=True)
def test_cli_question_no(self, mocked_input):
mocked_input.side_effect = ['N']
self.assertFalse(msg.cli_question('test'))

@patch('msg.input', create=True)
def test_cli_question_yes(self, mocked_input):
mocked_input.side_effect = ['Y']
self.assertTrue(msg.cli_question('test'))

@patch('msg.input', create=True)
def test_logos_acknowledge_question_empty(self, mocked_input):
config.DIALOG = 'curses'
mocked_input.side_effect = ['']
result = msg.logos_acknowledge_question('test', 'no')
self.assertTrue(result)

@patch('msg.input', create=True)
def test_logos_acknowledge_question_no(self, mocked_input):
config.DIALOG = 'curses'
mocked_input.side_effect = ['N']
result = msg.logos_acknowledge_question('test', 'no')
self.assertFalse(result)

@patch('msg.input', create=True)
def test_logos_acknowledge_question_yes(self, mocked_input):
config.DIALOG = 'curses'
mocked_input.side_effect = ['Y']
result = msg.logos_acknowledge_question('test', 'no')
self.assertTrue(result)

@patch('msg.input', create=True)
def test_logos_continue_question_yes(self, mocked_input):
config.DIALOG = 'curses'
mocked_input.side_effect = ['Y']
result = msg.logos_continue_question('test', 'no', None)
self.assertIsNone(result)
115 changes: 115 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import json
import shutil
import unittest
from pathlib import Path

import config
import utils


class TestUtils(unittest.TestCase):
def test_check_logos_release_version_false(self):
result = utils.check_logos_release_version('1.1.1', 1, 3)
self.assertFalse(result)

def test_check_logos_release_version_true(self):
result = utils.check_logos_release_version('1.1.1', 2, 3)
self.assertTrue(result)

def test_compare_logos_linux_installer_version_custom(self):
config.LLI_CURRENT_VERSION = '4.0.1'
config.LLI_LATEST_VERSION = '4.0.0-alpha.1'
status, _ = utils.compare_logos_linux_installer_version()
self.assertEqual(2, status)

def test_compare_logos_linux_installer_version_notset(self):
config.LLI_CURRENT_VERSION = None
config.LLI_LATEST_VERSION = '4.0.1'
status, _ = utils.compare_logos_linux_installer_version()
self.assertFalse(status)

def test_compare_logos_linux_installer_version_uptodate(self):
config.LLI_CURRENT_VERSION = '4.0.0-alpha.1'
config.LLI_LATEST_VERSION = '4.0.0-alpha.1'
status, _ = utils.compare_logos_linux_installer_version()
self.assertEqual(1, status)

def test_compare_logos_linux_installer_version_yes(self):
config.LLI_CURRENT_VERSION = '4.0.0-alpha.1'
config.LLI_LATEST_VERSION = '4.0.1'
status, _ = utils.compare_logos_linux_installer_version()
self.assertEqual(0, status)

def test_file_exists_false(self):
self.assertFalse(utils.file_exists('~/NotGonnaFindIt.exe'))

def test_file_exists_true(self):
self.assertTrue(utils.file_exists('~/.bashrc'))

def test_filter_versions(self):
allvers = ['1.0', '1.1', '1.2', '1.3', '1.4', '1.5']
valvers = ['1.0', '1.1', '1.2', '1.3',]
filvers = utils.filter_versions(allvers, 4, 2)
self.assertEqual(valvers, filvers)

def test_get_wine_options(self):
config.DIALOG = 'curses'
binaries = [
'/usr/bin/wine64.exe',
]
opts = utils.get_wine_options([], binaries)
opt_codes = [o[0] for o in opts]
choices = ["Recommended", "System", "Exit"]
self.assertEqual(choices, opt_codes)

def test_get_winebincode_appimage(self):
binary = 'test.AppImage'
code, _ = utils.get_winebin_code_and_desc(binary)
self.assertEqual('AppImage', code)

def test_get_winebincode_pol(self):
binary = 'test/PlayOnLinux/wine64.exe'
code, _ = utils.get_winebin_code_and_desc(binary)
self.assertEqual('PlayOnLinux', code)

def test_get_winebincode_proton(self):
binary = 'test/Proton/wine64.exe'
code, _ = utils.get_winebin_code_and_desc(binary)
self.assertEqual('Proton', code)

# def test_get_winebincode_recommended(self):
# pass

def test_get_winebincode_system(self):
binary = '/usr/bin/wine64.exe'
code, _ = utils.get_winebin_code_and_desc(binary)
self.assertEqual('System', code)

def test_have_dep(self):
self.assertTrue(utils.have_dep('which'))

def test_have_lib(self):
self.assertTrue(utils.have_lib('libgcc', None))


class TestUtilsConfigFile(unittest.TestCase):
def setUp(self):
self.cfg = Path('test/data/subdir/test_config.json')

def test_update_config_file(self):
utils.write_config(str(self.cfg))
utils.update_config_file(str(self.cfg), 'TARGETVERSION', '100')
with self.cfg.open() as f:
cfg_data = json.load(f)
self.assertEqual(cfg_data.get('TARGETVERSION'), '100')

def test_write_config_parentdir(self):
utils.write_config(str(self.cfg))
self.assertTrue(self.cfg.parent.is_dir())

def test_write_config_writedata(self):
utils.write_config(str(self.cfg))
self.assertTrue(self.cfg.read_text())

def tearDown(self):
shutil.rmtree(self.cfg.parent)

0 comments on commit da1e28d

Please sign in to comment.