From 42e30c8adc124b9afbf790ad15c0df169d4d7f76 Mon Sep 17 00:00:00 2001 From: mickwd Date: Wed, 21 Oct 2026 18:20:23 -0300 Subject: [PATCH] Migrate test_file_options and test_multiplatform to pytest --- tests/test_file_options.py | 265 ++++++++++++++++++------------------ tests/test_multiplatform.py | 111 ++++++++------- 2 files changed, 189 insertions(+), 187 deletions(-) diff --git a/tests/test_file_options.py b/tests/test_file_options.py index 5b63a2b..4e21b68 100644 --- a/tests/test_file_options.py +++ b/tests/test_file_options.py @@ -1,4 +1,4 @@ -# Copyright 2016 Facundo Batista, Nicolás Demarchi +# Copyright 2016-2026 Facundo Batista, Nicolás Demarchi # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published @@ -17,138 +17,141 @@ """Tests for file_options.""" import argparse -import unittest -from configparser import ConfigParser from unittest.mock import patch +import pytest + from fades import file_options -class OptionsFileTestCase(unittest.TestCase): - """Check file_options.options_from_file().""" - - def setUp(self): - self.argparser = argparse.ArgumentParser() - self.argparser.add_argument - self.argparser.add_argument('-f', '--foo', action='store_true') - self.argparser.add_argument('-b', '--bar', action='store') - self.argparser.add_argument('-d', '--dependency', action='append') - self.argparser.add_argument('positional', nargs='?', default=None) - - def build_parser(self, args): - config_parser = ConfigParser() - config_parser['fades'] = args - return config_parser - - @patch("fades.file_options.CONFIG_FILES", ('/foo/none', '/dev/null')) - def test_no_config_files(self): - args = self.argparser.parse_args([]) - result = file_options.options_from_file(args) - - self.assertEqual(args, result) - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) - @patch("configparser.ConfigParser.items") - def test_single_config_file_no_cli(self, mocked_parser): - mocked_parser.return_value = [('foo', 'true'), ('bar', 'hux')] - args = self.argparser.parse_args(['positional']) - result = file_options.options_from_file(args) - - self.assertTrue(result.foo) - self.assertEqual(result.bar, 'hux') - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) - @patch("configparser.ConfigParser.items") - def test_single_config_file_with_cli(self, mocked_parser): - mocked_parser.return_value = [('foo', 'false'), ('bar', 'hux'), ('no_in_cli', 'testing')] - args = self.argparser.parse_args(['--foo', '--bar', 'other', 'positional']) - result = file_options.options_from_file(args) - - self.assertTrue(result.foo) - self.assertEqual(result.bar, 'other') - self.assertEqual(result.no_in_cli, 'testing') - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) - @patch("configparser.ConfigParser.items") - def test_single_config_file_with_mergeable(self, mocked_parser): - mocked_parser.return_value = [('dependency', 'two')] - args = self.argparser.parse_args( - ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) - result = file_options.options_from_file(args) - - self.assertTrue(result.foo) - self.assertEqual(result.bar, 'other') - self.assertEqual(result.dependency, ['one', 'two']) - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) - @patch("configparser.ConfigParser.items") - def test_single_config_file_complex_mergeable(self, mocked_parser): - mocked_parser.return_value = [('dependency', 'requests>=2.1,<2.8,!=2.6.5')] - args = self.argparser.parse_args( - ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) - result = file_options.options_from_file(args) - - self.assertTrue(result.foo) - self.assertEqual(result.bar, 'other') - self.assertEqual(result.dependency, ['one', 'requests>=2.1,<2.8,!=2.6.5']) - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) - @patch("configparser.ConfigParser.items") - def test_two_config_file_with_mergeable(self, mocked_parser): - mocked_parser.side_effect = [ - [('dependency', 'two')], - [('dependency', 'three')], - ] - args = self.argparser.parse_args( - ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) - result = file_options.options_from_file(args) - - self.assertTrue(result.foo) - self.assertEqual(result.bar, 'other') - self.assertEqual(result.dependency, ['one', 'two', 'three']) - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) - @patch("configparser.ConfigParser.items") - def test_two_config_file_with_booleans(self, mocked_parser): - mocked_parser.side_effect = [ - [('foo', 'true')], - [('foo', 'false')], - ] - args = self.argparser.parse_args([]) - result = file_options.options_from_file(args) - - self.assertFalse(result.foo) - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) - @patch("configparser.ConfigParser.items") - def test_two_config_file_override_by_cli(self, mocked_parser): - mocked_parser.side_effect = [ - [('bar', 'no_this')], - [('bar', 'no_this_b')], - ] - args = self.argparser.parse_args(['--bar', 'this']) - result = file_options.options_from_file(args) - - self.assertEqual(result.bar, 'this') - self.assertIsInstance(args, argparse.Namespace) - - @patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini', 'mock3.ini')) - @patch("configparser.ConfigParser.items") - def test_three_config_file_override(self, mocked_parser): - mocked_parser.side_effect = [ - [('bar', 'no_this')], - [('bar', 'neither_this')], - [('bar', 'this')], - ] - args = self.argparser.parse_args([]) - result = file_options.options_from_file(args) - - self.assertEqual(result.bar, 'this') - self.assertIsInstance(args, argparse.Namespace) +@pytest.fixture +def argparser(): + """Build the argument parser used in the tests.""" + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', action='store_true') + parser.add_argument('-b', '--bar', action='store') + parser.add_argument('-d', '--dependency', action='append') + parser.add_argument('positional', nargs='?', default=None) + return parser + + +@patch("fades.file_options.CONFIG_FILES", ('/foo/none', '/dev/null')) +def test_no_config_files(argparser): + args = argparser.parse_args([]) + result = file_options.options_from_file(args) + + assert args == result + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) +@patch("configparser.ConfigParser.items") +def test_single_config_file_no_cli(mocked_parser, argparser): + mocked_parser.return_value = [('foo', 'true'), ('bar', 'hux')] + args = argparser.parse_args(['positional']) + result = file_options.options_from_file(args) + + assert result.foo + assert result.bar == 'hux' + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) +@patch("configparser.ConfigParser.items") +def test_single_config_file_with_cli(mocked_parser, argparser): + mocked_parser.return_value = [('foo', 'false'), ('bar', 'hux'), ('no_in_cli', 'testing')] + args = argparser.parse_args(['--foo', '--bar', 'other', 'positional']) + result = file_options.options_from_file(args) + + assert result.foo + assert result.bar == 'other' + assert result.no_in_cli == 'testing' + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) +@patch("configparser.ConfigParser.items") +def test_single_config_file_with_mergeable(mocked_parser, argparser): + mocked_parser.return_value = [('dependency', 'two')] + args = argparser.parse_args( + ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) + result = file_options.options_from_file(args) + + assert result.foo + assert result.bar == 'other' + assert result.dependency == ['one', 'two'] + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini',)) +@patch("configparser.ConfigParser.items") +def test_single_config_file_complex_mergeable(mocked_parser, argparser): + mocked_parser.return_value = [('dependency', 'requests>=2.1,<2.8,!=2.6.5')] + args = argparser.parse_args( + ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) + result = file_options.options_from_file(args) + + assert result.foo + assert result.bar == 'other' + assert result.dependency == ['one', 'requests>=2.1,<2.8,!=2.6.5'] + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) +@patch("configparser.ConfigParser.items") +def test_two_config_file_with_mergeable(mocked_parser, argparser): + mocked_parser.side_effect = [ + [('dependency', 'two')], + [('dependency', 'three')], + ] + args = argparser.parse_args( + ['--foo', '--bar', 'other', '--dependency', 'one', 'positional']) + result = file_options.options_from_file(args) + + assert result.foo + assert result.bar == 'other' + assert result.dependency == ['one', 'two', 'three'] + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) +@patch("configparser.ConfigParser.items") +def test_two_config_file_with_booleans(mocked_parser, argparser): + mocked_parser.side_effect = [ + [('foo', 'true')], + [('foo', 'false')], + ] + args = argparser.parse_args([]) + result = file_options.options_from_file(args) + + assert not result.foo + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini')) +@patch("configparser.ConfigParser.items") +def test_two_config_file_override_by_cli(mocked_parser, argparser): + mocked_parser.side_effect = [ + [('bar', 'no_this')], + [('bar', 'no_this_b')], + ] + args = argparser.parse_args(['--bar', 'this']) + result = file_options.options_from_file(args) + + assert result.bar == 'this' + assert isinstance(args, argparse.Namespace) + + +@patch("fades.file_options.CONFIG_FILES", ('mock.ini', 'mock2.ini', 'mock3.ini')) +@patch("configparser.ConfigParser.items") +def test_three_config_file_override(mocked_parser, argparser): + mocked_parser.side_effect = [ + [('bar', 'no_this')], + [('bar', 'neither_this')], + [('bar', 'this')], + ] + args = argparser.parse_args([]) + result = file_options.options_from_file(args) + + assert result.bar == 'this' + assert isinstance(args, argparse.Namespace) diff --git a/tests/test_multiplatform.py b/tests/test_multiplatform.py index 86eac15..6fd137c 100644 --- a/tests/test_multiplatform.py +++ b/tests/test_multiplatform.py @@ -19,8 +19,8 @@ import threading import time -import unittest -from pathlib import Path + +import pytest from fades.multiplatform import filelock @@ -46,57 +46,56 @@ def run(self): self.post_work = time.time() -class LockCacheTestCase(unittest.TestCase): - """Tests for the locking utility.""" - - def setUp(self): - self.test_path = Path("test_filelock") - - def tearDown(self): - if self.test_path.exists(): - self.test_path.unlink() - - def wait(self, lock_checker, attr_name): - """Wait at most a second for the LockChecker to end.""" - for i in range(10): - attr = getattr(lock_checker, attr_name) - if attr is not None: - # ended! - return - time.sleep(.3) - self.fail("LC didnt end: %s" % (lock_checker,)) - - def test_lock_alone(self): - lc = LockChecker(self.test_path) - lc.start() - lc.middle_work.set() - self.wait(lc, 'post_work') - - def test_lock_intermixed(self): - lc1 = LockChecker(self.test_path) - lc1.start() - self.wait(lc1, 'in_lock') - - lc2 = LockChecker(self.test_path) - lc2.start() - - lc1.middle_work.set() - self.wait(lc1, 'post_work') - - lc2.middle_work.set() - self.wait(lc2, 'post_work') - - # check LC 2 waited to enter - self.assertGreater(lc2.in_lock, lc1.post_work) - - def test_lock_exploding(self): - # get the lock and explode in the middle (then ignore the blast) - try: - with filelock(self.test_path): - raise ValueError("pumba") - except ValueError: - pass - - # get the lock again - with filelock(self.test_path): - pass +@pytest.fixture +def lock_path(tmp_path): + """Provide a path for the lock file.""" + return tmp_path / "test_filelock" + + +def wait(lock_checker, attr_name): + """Wait at most a second for the LockChecker to end.""" + for i in range(10): + attr = getattr(lock_checker, attr_name) + if attr is not None: + # ended! + return + time.sleep(.3) + pytest.fail("LC didnt end: %s" % (lock_checker,)) + + +def test_lock_alone(lock_path): + lc = LockChecker(lock_path) + lc.start() + lc.middle_work.set() + wait(lc, 'post_work') + + +def test_lock_intermixed(lock_path): + lc1 = LockChecker(lock_path) + lc1.start() + wait(lc1, 'in_lock') + + lc2 = LockChecker(lock_path) + lc2.start() + + lc1.middle_work.set() + wait(lc1, 'post_work') + + lc2.middle_work.set() + wait(lc2, 'post_work') + + # check LC 2 waited to enter + assert lc2.in_lock > lc1.post_work + + +def test_lock_exploding(lock_path): + # get the lock and explode in the middle (then ignore the blast) + try: + with filelock(lock_path): + raise ValueError("pumba") + except ValueError: + pass + + # get the lock again + with filelock(lock_path): + pass