Skip to content

Commit

Permalink
dev-tooling: introduce tox support
Browse files Browse the repository at this point in the history
- added tox.ini file for test environment management
- added yproject.toml file to project
- added test-requirements.txt
- added test case comments

Signed-off-by: Mustafa Kemal Gilor <[email protected]>
  • Loading branch information
xmkg committed Aug 1, 2023
1 parent fd32d3f commit d1c51bf
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 23 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# hotkdump
hotkdump is a tool for auto analysis of vmcores.

hotkdump is a tool for auto analysis of vmcores.

## How to run tests

Running `tox -e py{36,37,38,39,310,311}` in project root directory will run all unit tests, e.g.:

```bash
tox -e py310
```
47 changes: 47 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# reference: https://github.com/pypa/sampleproject/blob/main/pyproject.toml

[project]
name = "hotkdump"
version = "0.0.1"
description = "Linux kernel crashdump analysis tool"
readme = "README.md"
requires-python = ">=3.6"
license = {file = "LICENSE.txt"}
classifiers = [
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 3 - Alpha",

# Pick your license as you wish
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",

# Specify the Python versions you support here. In particular, ensure
# that you indicate you support Python 3. These classifiers are *not*
# checked by "pip install". See instead "python_requires" below.
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
]

[project.optional-dependencies]
test = ["pytest"]

[project.urls] # Optional
"Homepage" = "https://github.com/canonical/hotkdump"
"Bug Reports" = "https://github.com/canonical/hotkdump/issues"

[project.scripts] # Optional
hotkdump = "hkd.hkd_impl:main"

[build-system]
# These are the assumed default build requirements from pip:
# https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support
requires = ["setuptools>=43.0.0", "wheel"]
build-backend = "setuptools.build_meta"
15 changes: 15 additions & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pytest>=7
pytest-sugar
# The below are ubuntu-dev-tools's dependencies but unfortunately
# the requirements.txt file is not referred in setup.py so we have
# to list them here
### SOF `ubuntu-dev-tools` requirements.txt ###
python-debian
python-debianbts
distro-info
httplib2
launchpadlib
requests
setuptools
### EOF `ubuntu-dev-tools` requirements.txt ###
ubuntu-dev-tools@git+https://git.launchpad.net/ubuntu/+source/ubuntu-dev-tools@applied/ubuntu/focal-updates
Empty file added tests/__init__.py
Empty file.
77 changes: 65 additions & 12 deletions tests/test_hotkdump.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@
`hotkdump` class unit tests.
"""

import unittest
import hkd
import mock
from unittest import mock, TestCase

import textwrap

from utils import (
import hkd

from tests.utils import (
assert_has_no_such_calls,
mock_file,
mock_stat_obj
)


mock.Mock.assert_has_no_such_calls = assert_has_no_such_calls
mock_hdr = b'KDUMP \x01\x02\x03\x04sys\0node\0release\0#version-443\0machine\0domain\0\0'
MOCK_HDR = b'KDUMP \x01\x02\x03\x04sys\0node\0release\0#version-443\0machine\0domain\0\0'


@mock.patch.multiple(
Expand All @@ -40,10 +41,15 @@
"shutil",
which=lambda x: x
)
@mock.patch('builtins.open', mock_file(bytes=mock_hdr, name="name"))
class HotkdumpTest(unittest.TestCase):
@mock.patch('builtins.open', mock_file(bytes=MOCK_HDR, name="name"))
class HotkdumpTest(TestCase):
"""test hotkdump class public api"""

def test_default_construct(self):
"""Default-construct the class and verify
that the class variables are initialized
as expected.
"""
uut = hkd.hotkdump("1", "vmcore")
self.assertEqual(uut.case_number, "1")
self.assertEqual(uut.vmcore_file, "vmcore")
Expand All @@ -52,6 +58,10 @@ def test_default_construct(self):
self.assertEqual(uut.ddebs_path, hkd.default_ddebs_path)

def test_construct(self):
"""Explicitly construct the class with pre-determined
values and verify that the class variables are initialized
as expected.
"""
uut = hkd.hotkdump("1", "vmcore", "opf", "log", "ddeb")
self.assertEqual(uut.case_number, "1")
self.assertEqual(uut.vmcore_file, "vmcore")
Expand All @@ -60,6 +70,10 @@ def test_construct(self):
self.assertEqual(uut.ddebs_path, "ddeb")

def test_arch(self):
"""Test if the get_arcitecture() method returns the
correct architecture string for different `machine`
types.
"""
uut = hkd.hotkdump("1", "vmcore")
uut.kdump_header.machine = "x86_64"
self.assertEqual(uut.get_architecture(), "amd64")
Expand All @@ -68,8 +82,11 @@ def test_arch(self):
uut.kdump_header.machine = "invalid"
self.assertRaises(NotImplementedError, uut.get_architecture)

@mock.patch('builtins.open', mock_file(bytes=mock_hdr, name="name"))
@mock.patch('builtins.open', mock_file(bytes=MOCK_HDR, name="name"))
def test_kdump_hdr(self):
"""Test if the kdump_header has the correct values included
in the MOCK_HDR after opening the fake vmcore file
"""
uut = hkd.hotkdump("1", "vmcore")
self.assertEqual(uut.kdump_header.kdump_version, 67305985)
self.assertEqual(uut.kdump_header.domain, "domain")
Expand All @@ -81,6 +98,9 @@ def test_kdump_hdr(self):
self.assertEqual(uut.kdump_header.normalized_version, "version")

def test_find_crash_executable_symlink_exists(self):
"""Verify that the hotkdump uses the crash symlink on the root
folder if exists.
"""
uut = hkd.hotkdump("1", "vmcore")
with mock.patch.multiple("os.path",
dirname=lambda *a, **kw: "/root/dir",
Expand All @@ -90,6 +110,9 @@ def test_find_crash_executable_symlink_exists(self):
self.assertEqual(value, "/root/dir/../crash")

def test_find_crash_executable_nosymlink_but_path_exists(self):
"""Verify that the hotkdump uses the crash from PATH when
the root-dir `crash` symlink does not exists.
"""
uut = hkd.hotkdump("1", "vmcore")
with mock.patch.multiple("os.path",
dirname=lambda *a, **kw: "/root/dir",
Expand All @@ -100,6 +123,9 @@ def test_find_crash_executable_nosymlink_but_path_exists(self):
self.assertEqual(value, "/usr/mybin/crash")

def test_find_crash_executable_notfound(self):
"""Verify that the hotkdump raises an exception when crash
executable could not be found.
"""
uut = hkd.hotkdump("1", "vmcore")
with mock.patch.multiple("os.path",
dirname=lambda *a, **kw: "/root/dir",
Expand All @@ -110,6 +136,9 @@ def test_find_crash_executable_notfound(self):
uut.find_crash_executable)

def test_write_crash_commands_file(self):
"""Verify that the hotkdump `write_crash_commands_file` writes the
correct commands file.
"""
uut = hkd.hotkdump("1", "vmcore")
uut.output_file = "hkd.test"
uut.temp_working_dir.name = "/tmpdir"
Expand Down Expand Up @@ -179,16 +208,23 @@ def update_contents(c):
self.assertEqual(contents, expected_output)

def test_exec(self):
"""Verify that the hotkdump calls the subprocess.Popen
with the correct arguments.
"""
uut = hkd.hotkdump("1", "vmcore")
with mock.patch("subprocess.Popen", mock.MagicMock()) as p:
uut.exec("a", "args", "wd")
p.assert_called_once_with(
"a args", shell=True, cwd="wd")

def test_switch_cwd(self):
pass
"""To be implemented later"""

def test_strip_tags(self):
"""Verify that the hotkdump is able to remove version-specific
suffixes and other shenanigans from the kernel version string
to obtain the "plain" version.
"""
test_cases_valid = [
# Regular tags
("5.4.0-146-generic", "5.4.0-146"),
Expand Down Expand Up @@ -253,6 +289,11 @@ def test_strip_tags(self):
@mock.patch("os.utime")
@mock.patch("hkd.hkd_impl.PullPkg")
def test_maybe_download_vmlinux_ddeb(self, mock_pullpkg, mock_utime):
"""Verify that the hotkdump:
- calls the PullPkg when the ddeb is absent
- does not call the PullPkg when the ddeb is present
- raises an ExceptionWithLog when PullPkg fails
"""
# Set up mock return values
mock_pull = mock.MagicMock()
mock_pullpkg.return_value.pull = mock_pull
Expand All @@ -278,7 +319,8 @@ def test_maybe_download_vmlinux_ddeb(self, mock_pullpkg, mock_utime):

# Assert that pullpkg was invoked with the correct arguments
mock_pull.assert_called_once_with(
["--distro", "ubuntu", "--arch", "amd64", "--pull", "ddebs", "linux-image-unsigned-5.15.0-1030-gcp", "5.15.0-1030.37"])
["--distro", "ubuntu", "--arch", "amd64", "--pull",
"ddebs", "linux-image-unsigned-5.15.0-1030-gcp", "5.15.0-1030.37"])

# Assert that the expected ddeb file path was returned
self.assertEqual(result, expected_ddeb_path)
Expand Down Expand Up @@ -310,6 +352,9 @@ def test_maybe_download_vmlinux_ddeb(self, mock_pullpkg, mock_utime):
uut.maybe_download_vmlinux_ddeb()

def test_post_run_ddeb_count_policy(self):
"""Verify that the hotkdump executes the file
count policy after execution as per configured.
"""
# Set up test data
uut = hkd.hotkdump("1", "vmcore")
uut.ddebs_path = "/path/to/ddebs"
Expand Down Expand Up @@ -368,6 +413,9 @@ def test_post_run_ddeb_count_policy(self):
mock_remove.assert_not_called()

def test_post_run_ddeb_age_policy(self):
"""Verify that the hotkdump executes the file
age policy after execution as per configured.
"""
# Set up test data
uut = hkd.hotkdump("1", "vmcore")
uut.ddebs_path = "/path/to/ddebs"
Expand All @@ -391,9 +439,8 @@ def test_post_run_ddeb_age_policy(self):
"/path/to/ddebs/file4.ddeb": {"atime": 0, "size": 3150},
# 50000 bytes in total
})

mock_listdir.reset_mock()
uut.post_run()

mock_listdir.assert_called_once_with('/path/to/ddebs')

expected_calls = [
Expand All @@ -410,6 +457,9 @@ def test_post_run_ddeb_age_policy(self):
mock_remove.assert_has_no_such_calls(not_expected_calls)

def test_post_run_ddeb_total_size_policy(self):
"""Verify that the hotkdump executes the file
total size policy after execution as per configured.
"""
# Set up test data
uut = hkd.hotkdump("1", "vmcore")
uut.ddebs_path = "/path/to/ddebs"
Expand Down Expand Up @@ -457,6 +507,9 @@ def test_post_run_ddeb_total_size_policy(self):
mock_remove.assert_has_no_such_calls(not_expected_calls)

def test_post_run_ddeb_retention_disabled(self):
"""Verify that the hotkdump removes the ddeb
files post-run when the file retention is disabled.
"""
uut = hkd.hotkdump("1", "vmcore")
uut.ddebs_path = "/path/to/ddebs"
uut.ddeb_retention_enabled = False
Expand Down
32 changes: 22 additions & 10 deletions tests/test_kdump_file_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
"""

import os
import mock
import unittest
from unittest import mock, TestCase

import hkd
from utils import mock_file

from tests.utils import mock_file


mock_hdr = b'KDUMP \x01\x02\x03\x04sys\0node\0release\0#version-443\0machine\0domain\0\0'
mock_hdr_shifted = os.urandom(8184) + mock_hdr
mock_hdr_invalid_no_sig = os.urandom(4096)
MOCK_HDR = b'KDUMP \x01\x02\x03\x04sys\0node\0release\0#version-443\0machine\0domain\0\0'
MOCK_HDR_SHIFTED = os.urandom(8184) + MOCK_HDR
MOCK_HDR_INVALID_NO_SIG = os.urandom(4096)


@mock.patch.multiple(
Expand All @@ -36,10 +37,14 @@
"shutil",
which=lambda x: x
)
class HotkdumpKdumpHdrTest(unittest.TestCase):
class HotkdumpKdumpHdrTest(TestCase):
"""kdump header parsing tests"""

@mock.patch('builtins.open', mock_file(bytes=mock_hdr, name="name"))
@mock.patch('builtins.open', mock_file(bytes=MOCK_HDR, name="name"))
def test_kdump_hdr(self):
"""Test kdump file header parsing with
a correct header.
"""
uut = hkd.kdump_file_header("dummy")
self.assertEqual(uut.kdump_version, 67305985)
self.assertEqual(uut.domain, "domain")
Expand All @@ -50,8 +55,12 @@ def test_kdump_hdr(self):
self.assertEqual(uut.version, "#version-443")
self.assertEqual(uut.normalized_version, "version")

@mock.patch('builtins.open', mock_file(bytes=mock_hdr_shifted, name="name"))
@mock.patch('builtins.open', mock_file(bytes=MOCK_HDR_SHIFTED, name="name"))
def test_kdump_hdr_shifted(self):
"""Test kdump file header parsing with
a correct header, but shifted forward.
(i.e. to simulate makedumpfile header)
"""
uut = hkd.kdump_file_header("dummy")
self.assertEqual(uut.kdump_version, 67305985)
self.assertEqual(uut.domain, "domain")
Expand All @@ -62,7 +71,10 @@ def test_kdump_hdr_shifted(self):
self.assertEqual(uut.version, "#version-443")
self.assertEqual(uut.normalized_version, "version")

@mock.patch('builtins.open', mock_file(bytes=mock_hdr_invalid_no_sig, name="name"))
@mock.patch('builtins.open', mock_file(bytes=MOCK_HDR_INVALID_NO_SIG, name="name"))
def test_kdump_hdr_no_sig(self):
"""Test kdump file header parsing with
garbage input.
"""
self.assertRaises(hkd.ExceptionWithLog,
hkd.hotkdump, "1", "vmcore")
11 changes: 11 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[tox]
env_list = py{36,37,38,39,310,311}
isolated_build=true
skipsdist = True

[testenv]
description = run the test suite with pytest
deps =
-r{toxinidir}/test-requirements.txt
commands =
pytest {posargs:tests}

0 comments on commit d1c51bf

Please sign in to comment.