Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mocking-based unit tests for the stop command #68

Merged
merged 49 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2ab3932
Add files via upload
SimonL22 Aug 14, 2024
ac200b0
Update test_status_execute_mocken.py
SimonL22 Aug 14, 2024
1704173
Update test_status_execute_mocken.py
SimonL22 Aug 14, 2024
953358f
Update test_status_execute_mocken.py
SimonL22 Aug 14, 2024
96ddcac
Update test_status_execute_mocken.py
SimonL22 Aug 14, 2024
12537dd
Update test_status_execute_mocken.py
SimonL22 Aug 20, 2024
6a8046d
Create pytest.yml
SimonL22 Aug 20, 2024
90fc463
Merge branch 'ad-freiburg:main' into test_status_execute_mocken
SimonL22 Aug 21, 2024
6704850
Create test_stop_execute
SimonL22 Aug 21, 2024
b58d7dd
Update and rename test/qlever/test_status_execute_mocken.py to test/q…
SimonL22 Aug 21, 2024
d46e928
Update test_status_execute_mocken.py
SimonL22 Aug 21, 2024
a62da4d
Add files via upload
SimonL22 Aug 21, 2024
98811a8
Update test_status_other_methods.py
SimonL22 Aug 26, 2024
fa1fb73
Add files via upload
SimonL22 Aug 27, 2024
6fe6558
Update pytest.yml
SimonL22 Aug 27, 2024
00a4100
Update test_stop_execute.py
SimonL22 Aug 27, 2024
9c87e3c
Add files via upload
SimonL22 Aug 31, 2024
b18c36f
Merge branch 'ad-freiburg:main' into test_status_execute_mocken
SimonL22 Aug 31, 2024
e56301c
Merge branch 'ad-freiburg:main' into test_stop_execute_mocken
SimonL22 Aug 31, 2024
e50ef8b
Update pytest.yml
SimonL22 Sep 10, 2024
549132c
Update pytest.yml
SimonL22 Sep 10, 2024
c22f8db
Update pytest.yml
SimonL22 Sep 10, 2024
50468f2
Update pytest.yml
SimonL22 Sep 10, 2024
bbc593a
Update and rename test_status_execute_mocken.py to test_status_execut…
SimonL22 Sep 10, 2024
47ed435
Update test_status_execute.py
SimonL22 Sep 12, 2024
7a656ea
Update test_status_other_methods.py
SimonL22 Sep 12, 2024
833bad6
Update test_status_execute.py
SimonL22 Sep 12, 2024
f6c759d
Update test_status_other_methods.py
SimonL22 Sep 12, 2024
d3c020b
Delete test/qlever/commands/test_stop_execute
SimonL22 Sep 12, 2024
804004a
Update test_stop_execute.py
SimonL22 Sep 18, 2024
d8eb841
Update test_stop_other_methods.py
SimonL22 Sep 18, 2024
7faf04c
Update test_stop_other_methods.py
SimonL22 Sep 18, 2024
b8740dc
Update test_stop_other_methods.py
SimonL22 Oct 3, 2024
2954ec3
Merge branch 'ad-freiburg:main' into test_stop_with_mocking
SimonL22 Oct 9, 2024
c1fd7b7
Update test_status_other_methods.py
SimonL22 Oct 9, 2024
fb7bf53
Update test_status_execute.py
SimonL22 Oct 9, 2024
80d0c48
Update test_status_execute.py
SimonL22 Oct 9, 2024
00618dc
Merge branch 'ad-freiburg:main' into test_status_with_mocking
SimonL22 Nov 3, 2024
80c5d55
Merge branch 'ad-freiburg:main' into test_stop_with_mocking
SimonL22 Nov 3, 2024
8144b1f
Update test_stop_execute.py
SimonL22 Dec 23, 2024
e446092
Merge branch 'ad-freiburg:main' into test_status_with_mocking
SimonL22 Dec 23, 2024
3c3f150
Merge branch 'ad-freiburg:main' into test_stop_with_mocking
SimonL22 Dec 23, 2024
6ef67ed
formatting
joka921 Dec 23, 2024
1cf6d16
fix tests
joka921 Dec 23, 2024
2fc801a
formatting fixed
joka921 Dec 23, 2024
5fb5ab0
also sort the imports via ruff
joka921 Dec 23, 2024
db42c93
move files
joka921 Dec 23, 2024
3ffaf2e
Merge branch 'test_status_with_mocking' into test_stop_with_mocking
joka921 Dec 23, 2024
5bafc7d
format
joka921 Dec 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions test/qlever/test_stop_execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
from __future__ import annotations
import unittest
from unittest.mock import patch, MagicMock
from qlever.commands.stop import StopCommand


class TestStopCommand(unittest.TestCase):

@patch('qlever.commands.stop.StatusCommand.execute')
@patch('psutil.process_iter')
@patch('qlever.containerize.Containerize.stop_and_remove_container')
@patch('qlever.commands.stop.StopCommand.show')
def test_execute_no_matching_processes_or_containers(self, mock_show,
mock_stop_and_remove_container,
mock_process_iter,
mock_status_execute):
# Setup args
args = MagicMock()
args.cmdline_regex = "ServerMain.* -i [^ ]*%%NAME%%"
args.name = "TestName"
args.no_containers = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (for this whole file) we agreed to split up the execute function, to make it easier testable
(separate functions for long if or try statements etc.
I am sure, if you do this , you can simplify the tests (first change the implementations, tests should still work, then simplify the tests).

args.server_container = "test_container"
args.show = False

# Replace the regex placeholder
expected_regex = args.cmdline_regex.replace("%%NAME%%", args.name)

# Mock process_iter to return no matching processes
mock_process_iter.return_value = []

# Instantiate the StopCommand
sc = StopCommand()

# Execute the function
result = sc.execute(args)

# Assertions
mock_show.assert_called_once_with(
f'Checking for processes matching "{expected_regex}"',
only_show=False)
mock_process_iter.assert_called_once()
mock_stop_and_remove_container.assert_not_called()
mock_status_execute.assert_called_once_with(args)
self.assertFalse(result)

@patch('qlever.commands.stop.StatusCommand.execute')
@patch('psutil.process_iter')
@patch('qlever.containerize.Containerize.stop_and_remove_container')
@patch('qlever.commands.stop.StopCommand.show')
def test_execute_with_matching_process(self, mock_show,
mock_stop_and_remove_container,
mock_process_iter,
mock_status_execute):
# Setup args
args = MagicMock()
args.cmdline_regex = "ServerMain.* -i [^ ]*%%NAME%%"
args.name = "TestName"
args.no_containers = True
args.server_container = "test_container"
args.show = False

# Replace the regex placeholder
expected_regex = args.cmdline_regex.replace("%%NAME%%", args.name)

# Creating mock psutil.Process objects with necessary attributes
mock_process = MagicMock()
# to test with real psutil.process objects use this:

mock_process.as_dict.return_value = {
'cmdline': ['ServerMain', '-i', '/some/path/TestName'],
'pid': 1234,
'username': 'test_user'
}

mock_process_iter.return_value = [mock_process]

# Mock process.kill to simulate successful process termination
mock_process.kill.return_value = None

# Instantiate the StopCommand
sc = StopCommand()

# Execute the function
result = sc.execute(args)

# Assertions
mock_show.assert_called_once_with(
f'Checking for processes matching "{expected_regex}"',
only_show=False)
mock_process_iter.assert_called_once()
mock_stop_and_remove_container.assert_not_called()
mock_process.kill.assert_called_once()
mock_status_execute.assert_not_called()
self.assertTrue(result)

@patch('qlever.commands.stop.StatusCommand.execute')
@patch('psutil.process_iter')
@patch('qlever.containerize.Containerize.stop_and_remove_container')
@patch('qlever.commands.stop.StopCommand.show')
def test_execute_with_containers(self, mock_show,
mock_stop_and_remove_container,
mock_process_iter, mock_status_execute):
# Setup args
args = MagicMock()
args.cmdline_regex = "ServerMain.* -i [^ ]*%%NAME%%"
args.name = "TestName"
args.no_containers = False
args.server_container = "test_container"
args.show = False

# Replace the regex placeholder
expected_regex = args.cmdline_regex.replace("%%NAME%%", args.name)

# Mocking container stop and removal
mock_stop_and_remove_container.return_value = True

# Instantiate the StopCommand
sc = StopCommand()

# Execute the function
result = sc.execute(args)

# Assertions
mock_show.assert_called_once_with(
f'Checking for processes matching "{expected_regex}" and for'
f' Docker container with name "{args.server_container}"',
only_show=False)
mock_process_iter.assert_not_called()
mock_stop_and_remove_container.assert_called_once()
mock_status_execute.assert_not_called()
self.assertTrue(result)

@patch('qlever.commands.stop.StatusCommand.execute')
@patch('psutil.process_iter')
@patch('qlever.containerize.Containerize.stop_and_remove_container')
@patch('qlever.commands.stop.StopCommand.show')
def test_execute_with_no_containers_and_no_matching_process(self,
mock_show,
mock_stop_and_remove_container,
mock_process_iter,
mock_status_execute):
# Setup args
args = MagicMock()
args.cmdline_regex = "ServerMain.* -i [^ ]*%%NAME%%"
args.name = "TestName"
args.no_containers = False
args.server_container = "test_container"
args.show = False

# Replace the regex placeholder
expected_regex = args.cmdline_regex.replace("%%NAME%%", args.name)

# Mock process_iter to return no matching processes
mock_process_iter.return_value = []

# Mock container stop and removal to return False (no container found)
mock_stop_and_remove_container.return_value = False

# Instantiate the StopCommand
sc = StopCommand()

# Execute the function
result = sc.execute(args)

# Assertions
mock_show.assert_called_once_with(
f'Checking for processes matching "{expected_regex}" and for'
f' Docker container with name "{args.server_container}"',
only_show=False)
mock_process_iter.assert_called_once()
mock_stop_and_remove_container.assert_called()
mock_status_execute.assert_called_once_with(args)
self.assertFalse(result)

@patch('qlever.commands.stop.StatusCommand.execute')
@patch('psutil.process_iter')
@patch('qlever.containerize.Containerize.stop_and_remove_container')
@patch('qlever.commands.stop.StopCommand.show')
@patch('qlever.commands.stop.show_process_info')
def test_execute_with_error_killing_process(self, mock_show_process_info,
mock_show,
mock_stop_and_remove_container,
mock_process_iter,
mock_status_execute):
# Setup args
args = MagicMock()
args.cmdline_regex = "ServerMain.* -i [^ ]*%%NAME%%"
args.name = "TestName"
args.no_containers = True
args.server_container = "test_container"
args.show = False

# Replace the regex placeholder
expected_regex = args.cmdline_regex.replace("%%NAME%%", args.name)

# Creating mock psutil.Process objects with necessary attributes
mock_process = MagicMock()
mock_process.as_dict.return_value = {
'cmdline': ['ServerMain', '-i', '/some/path/TestName'],
'pid': 1234,
'create_time': 1234567890,
'memory_info': MagicMock(rss=1024 * 1024 * 512),
'username': 'test_user'
}
mock_process_iter.return_value = [mock_process]

# Mock process.kill to raise an exception
mock_process.kill.side_effect = Exception('Test')

# Instantiate the StopCommand
sc = StopCommand()

# Execute the function
result = sc.execute(args)

# Assertions
mock_show.assert_called_once_with(
f'Checking for processes matching "{expected_regex}"',
only_show=False)
mock_process_iter.assert_called_once()
mock_stop_and_remove_container.assert_not_called()
mock_process.kill.assert_called_once()
mock_show_process_info.assert_called_once_with(mock_process, "",
show_heading=True)
mock_status_execute.assert_not_called()
self.assertFalse(result)
49 changes: 49 additions & 0 deletions test/qlever/test_stop_other_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import unittest
from qlever.commands.stop import StopCommand
import argparse


class TestStopCommand(unittest.TestCase):
def test_description(self):
result = StopCommand().description()
self.assertEqual(result, "Stop QLever server for a "
"given datasedataset or port")

def test_should_have_qleverfile(self):
result = StopCommand().should_have_qleverfile()
assert result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some self.assertTrue(StopCommand().should_have_qleverfile()) opportunity here
(one line less + more important always use the same assertion mechanims consistently.


def test_relevant_qleverfile_arguments(self):
result = StopCommand().relevant_qleverfile_arguments()
self.assertEqual(result, {"data": ["name"],
"server": ["port"],
"runtime": ["server_container"]})

def test_additional_arguments(self):
# Create an instance of StopCommand
sc = StopCommand()

# Create a parser and a subparser
parser = argparse.ArgumentParser()
subparser = parser.add_argument_group('test')
# Call the method
sc.additional_arguments(subparser)
# Parse an empty argument list to see the default
args = parser.parse_args([])

# Test that the default value for cmdline_regex is set correctly
self.assertEqual(args.cmdline_regex, "ServerMain.* -i "
"[^ ]*%%NAME%%")

# Test that the help text for cmdline_regex is correctly set
argument_help = subparser._group_actions[-2].help
self.assertEqual(argument_help, "Show only processes where "
"the command line matches this regex")

# Test that the default value for no-containers is set correctly
self.assertEqual(args.no_containers, False)

# Test that the help text for no-containers is correctly set
argument_help = subparser._group_actions[-1].help
self.assertEqual(argument_help, "Do not look for containers, "
"only for native processes")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be simpler to also mock the ArgumentParser for the testing of the add_argument_group.
Otherwise you could use implement some abstraction, that does the assertions on the actual ArgParse object, but abstracts away all the stuff with the _group_actions[-1] etc.
But maybe I am overthinking.