From 3847ca085bbeff3b396ee34858bccca12897fa17 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Fri, 20 Mar 2026 22:34:51 +0530 Subject: [PATCH 1/5] chore: added new dependencies for test Signed-off-by: rohansen856 --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 760e79b..96cf364 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ PyGithub>=1.58.0 PyYAML>=5.1 +pytest>=7.4.0 +pytest-mock>=3.11.0 +pytest-cov>=4.1.0 From 6991f01214fa686cbf9907c2532a691dbabd329b Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Fri, 20 Mar 2026 22:38:06 +0530 Subject: [PATCH 2/5] tests: added tests for script builder Signed-off-by: rohansen856 --- tests/__init__.py | 0 tests/conftest.py | 42 +++++ tests/test_script_builder.py | 332 +++++++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_script_builder.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..286727a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,42 @@ +# Copyright Contributors to the Mainframe Software Hub for Linux Project. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import os +import tempfile +import shutil + + +@pytest.fixture +def temp_repo_dir(): + """Create a temporary directory for repository simulation.""" + temp_dir = tempfile.mkdtemp() + yield temp_dir + shutil.rmtree(temp_dir, ignore_errors=True) + + +@pytest.fixture +def mock_subprocess_run(mocker): + """Mock subprocess.run to avoid actual command execution.""" + return mocker.patch('subprocess.run') + + +@pytest.fixture +def mock_os_listdir(mocker): + """Mock os.listdir for directory content simulation.""" + return mocker.patch('os.listdir') + + +@pytest.fixture +def mock_os_path_exists(mocker): + """Mock os.path.exists for file existence simulation.""" + return mocker.patch('os.path.exists') + + +@pytest.fixture +def sample_artifact(): + """Sample artifact configuration for testing.""" + return { + "version": "1.0.0", + "type": "binary" + } diff --git a/tests/test_script_builder.py b/tests/test_script_builder.py new file mode 100644 index 0000000..bb9b11f --- /dev/null +++ b/tests/test_script_builder.py @@ -0,0 +1,332 @@ +# Copyright Contributors to the Mainframe Software Hub for Linux Project. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import os +from unittest.mock import patch, MagicMock +from builders.script.loz_script_builder import ScriptBuilder + + +class TestScriptBuilder: + """Test ScriptBuilder build and publish methods.""" + + def test_build_success(self, temp_repo_dir, mocker): + """Test successful script-based build.""" + # Setup script repository + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + script_path = os.path.join(temp_repo_dir, "build.sh") + with open(script_path, "w") as f: + f.write("#!/bin/bash\necho 'Building...'") + + # Create expected output file + output_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + with open(output_path, "w") as f: + f.write("fake tarball") + + # Mock subprocess.run + mock_run = mocker.patch('subprocess.run') + + # Build + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "build.sh", + "args": "--platform s390x" + } + } + result_path = builder.build(temp_repo_dir, "test-app", artifact) + + # Assertions + assert os.path.normpath(result_path) == os.path.normpath(output_path) + mock_run.assert_called_once() + + # Verify Docker command + docker_call = mock_run.call_args + assert "docker" in docker_call[0][0] + assert "run" in docker_call[0][0] + assert "bash" in docker_call[0][0] + + def test_build_with_docker_required(self, temp_repo_dir, mocker): + """Test build when Docker is required within container.""" + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + script_path = os.path.join(temp_repo_dir, "build.sh") + with open(script_path, "w") as f: + f.write("#!/bin/bash\necho 'Building with Docker...'") + + output_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + with open(output_path, "w") as f: + f.write("fake tarball") + + # Set environment variables + mocker.patch.dict(os.environ, { + 'DOCKER_USERNAME': 'testuser', + 'DOCKER_PASSWORD': 'testpass', + 'GH_TOKEN': 'ghtoken', + 'GH_PUSH_USER': 'pushuser' + }) + + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "build.sh", + "docker_required": True + } + } + builder.build(temp_repo_dir, "test-app", artifact) + + # Verify Docker socket is mounted + docker_call = mock_run.call_args + assert "/var/run/docker.sock:/var/run/docker.sock" in docker_call[0][0] + assert "-e" in docker_call[0][0] + + def test_build_missing_script_path(self, temp_repo_dir): + """Test build fails when script path is not specified.""" + builder = ScriptBuilder() + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts" + # Missing path + } + } + + with pytest.raises(ValueError, match="build_script.path required"): + builder.build(temp_repo_dir, "test-app", artifact) + + def test_build_script_repo_not_found(self, temp_repo_dir): + """Test build fails when script repository is not found.""" + builder = ScriptBuilder() + # Don't set script_repo_paths + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "missing-repo", + "path": "build.sh" + } + } + + with pytest.raises(ValueError, match="Script repository missing-repo not found"): + builder.build(temp_repo_dir, "test-app", artifact) + + def test_build_script_file_not_found(self, temp_repo_dir): + """Test build fails when script file doesn't exist.""" + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "nonexistent.sh" + } + } + + with pytest.raises(FileNotFoundError, match="Script .* not found"): + builder.build(temp_repo_dir, "test-app", artifact) + + def test_build_output_not_created(self, temp_repo_dir, mocker): + """Test build fails when expected output is not created.""" + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + script_path = os.path.join(temp_repo_dir, "build.sh") + with open(script_path, "w") as f: + f.write("#!/bin/bash\necho 'Building...'") + + # Don't create output file + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "build.sh" + } + } + + with pytest.raises(FileNotFoundError, match="Expected output .* not found"): + builder.build(temp_repo_dir, "test-app", artifact) + + def test_build_subprocess_error(self, temp_repo_dir, mocker): + """Test build handles subprocess errors gracefully.""" + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + script_path = os.path.join(temp_repo_dir, "build.sh") + with open(script_path, "w") as f: + f.write("#!/bin/bash\nexit 1") + + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError( + 1, 'bash', stderr=b'Script error' + )) + + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "build.sh" + } + } + + with pytest.raises(subprocess.CalledProcessError): + builder.build(temp_repo_dir, "test-app", artifact) + + def test_build_custom_docker_image(self, temp_repo_dir, mocker): + """Test build with custom Docker image.""" + script_repo_path = os.path.join(temp_repo_dir, "scripts") + os.makedirs(script_repo_path, exist_ok=True) + + script_path = os.path.join(temp_repo_dir, "build.sh") + with open(script_path, "w") as f: + f.write("#!/bin/bash\necho 'Building...'") + + output_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + with open(output_path, "w") as f: + f.write("fake tarball") + + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + builder.set_script_repo_paths({"linux-on-ibm-z-scripts": script_repo_path}) + + artifact = { + "version": "1.0.0", + "build_script": { + "repo_name": "linux-on-ibm-z-scripts", + "path": "build.sh", + "docker_image": "alpine:latest" + } + } + builder.build(temp_repo_dir, "test-app", artifact) + + # Verify custom Docker image + docker_call = mock_run.call_args + assert "alpine:latest" in docker_call[0][0] + + @patch('lib.checksum.generate_checksum') + def test_publish_success(self, mock_checksum, temp_repo_dir, mocker): + """Test successful artifact publishing.""" + artifact_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + with open(artifact_path, "w") as f: + f.write("fake tarball") + + mock_checksum.return_value = "abc123" + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "test-app", artifact) + + # Assertions + mock_checksum.assert_called_once_with(artifact_path) + mock_run.assert_called_once() + + # Verify gh release create command + gh_call = mock_run.call_args + assert "gh" in gh_call[0][0] + assert "release" in gh_call[0][0] + assert "create" in gh_call[0][0] + assert "v1.0.0" in gh_call[0][0] + + @patch('lib.checksum.generate_checksum') + def test_publish_with_rpm(self, mock_checksum, temp_repo_dir, mocker): + """Test publishing with additional RPM artifact.""" + artifact_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + rpm_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.rpm") + + with open(artifact_path, "w") as f: + f.write("fake tarball") + with open(rpm_path, "w") as f: + f.write("fake rpm") + + mock_checksum.return_value = "abc123" + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "test-app", artifact) + + # Should call gh release twice: create + upload + assert mock_run.call_count == 2 + + # Verify upload call + upload_call = mock_run.call_args_list[1] + assert "upload" in upload_call[0][0] + # Normalize paths for cross-platform comparison + uploaded_file = os.path.normpath(upload_call[0][0][-1]) + assert os.path.normpath(rpm_path) == uploaded_file + + @patch('lib.checksum.generate_checksum') + def test_publish_with_deb(self, mock_checksum, temp_repo_dir, mocker): + """Test publishing with additional DEB artifact.""" + artifact_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + deb_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.deb") + + with open(artifact_path, "w") as f: + f.write("fake tarball") + with open(deb_path, "w") as f: + f.write("fake deb") + + mock_checksum.return_value = "abc123" + mock_run = mocker.patch('subprocess.run') + + builder = ScriptBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "test-app", artifact) + + # Should call gh release twice: create + upload + assert mock_run.call_count == 2 + + # Verify upload call + upload_call = mock_run.call_args_list[1] + assert "upload" in upload_call[0][0] + # Normalize paths for cross-platform comparison + uploaded_file = os.path.normpath(upload_call[0][0][-1]) + assert os.path.normpath(deb_path) == uploaded_file + + @patch('lib.checksum.generate_checksum') + def test_publish_subprocess_error(self, mock_checksum, temp_repo_dir, mocker): + """Test publish handles subprocess errors gracefully.""" + artifact_path = os.path.join(temp_repo_dir, "test-app-1.0.0-linux-s390x.tar.gz") + with open(artifact_path, "w") as f: + f.write("fake tarball") + + mock_checksum.return_value = "abc123" + + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError( + 1, 'gh', stderr=b'GitHub error' + )) + + builder = ScriptBuilder() + artifact = {"version": "1.0.0"} + + # Note: The actual code doesn't have error handling for publish in ScriptBuilder + # This test documents that behavior + with pytest.raises(subprocess.CalledProcessError): + builder.publish(artifact_path, "test-app", artifact) From 2739d0efe4acc901bb501769589330558826a20a Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Fri, 20 Mar 2026 22:38:48 +0530 Subject: [PATCH 3/5] tests: added tests for go and java binary builder Signed-off-by: rohansen856 --- tests/test_go_binary_builder.py | 183 ++++++++++++++++++++ tests/test_java_binary_builder.py | 274 ++++++++++++++++++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 tests/test_go_binary_builder.py create mode 100644 tests/test_java_binary_builder.py diff --git a/tests/test_go_binary_builder.py b/tests/test_go_binary_builder.py new file mode 100644 index 0000000..9ccd84e --- /dev/null +++ b/tests/test_go_binary_builder.py @@ -0,0 +1,183 @@ +# Copyright Contributors to the Mainframe Software Hub for Linux Project. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import os +from unittest.mock import patch +from builders.binary.go_binary_builder import GoBinaryBuilder + + +class TestGoBinaryBuilder: + """Test GoBinaryBuilder build and publish methods.""" + + def test_build_success(self, temp_repo_dir, mocker): + """Test successful Go binary build.""" + # Mock subprocess.run + mock_run = mocker.patch('subprocess.run') + + # Build + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + result_path = builder.build(temp_repo_dir, "go-app", artifact) + + # Assertions + expected_path = os.path.join(temp_repo_dir, "build", "go-app_1.0.0_s390x") + assert os.path.normpath(result_path) == os.path.normpath(expected_path) + + # Verify Docker command was called + mock_run.assert_called_once() + docker_call = mock_run.call_args + assert "docker" in docker_call[0][0] + assert "run" in docker_call[0][0] + assert "--rm" in docker_call[0][0] + assert "ubuntu:22.04" in docker_call[0][0] + assert "go" in docker_call[0][0] + assert "build" in docker_call[0][0] + # Check that expected path appears in command (may have different separators) + assert any(os.path.normpath(expected_path) == os.path.normpath(arg) for arg in docker_call[0][0]) + + def test_build_with_custom_docker_image(self, temp_repo_dir, mocker): + """Test build with custom Docker image.""" + mock_run = mocker.patch('subprocess.run') + + builder = GoBinaryBuilder() + artifact = { + "version": "2.0.0", + "docker_image": "golang:1.21-alpine" + } + builder.build(temp_repo_dir, "custom-go-app", artifact) + + # Verify custom Docker image is used + docker_call = mock_run.call_args + assert "golang:1.21-alpine" in docker_call[0][0] + + def test_build_default_version(self, temp_repo_dir, mocker): + """Test build with default version when not specified.""" + mock_run = mocker.patch('subprocess.run') + + builder = GoBinaryBuilder() + artifact = {} # No version specified + result_path = builder.build(temp_repo_dir, "go-app", artifact) + + # Should use default version 1.0 + expected_path = os.path.join(temp_repo_dir, "build", "go-app_1.0_s390x") + assert os.path.normpath(result_path) == os.path.normpath(expected_path) + + def test_build_subprocess_error(self, temp_repo_dir, mocker): + """Test build handles subprocess errors gracefully.""" + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError( + 1, 'docker', stderr=b'Docker error' + )) + + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(subprocess.CalledProcessError): + builder.build(temp_repo_dir, "error-app", artifact) + + def test_build_creates_output_directory(self, temp_repo_dir, mocker): + """Test that build creates the build directory if it doesn't exist.""" + mock_run = mocker.patch('subprocess.run') + + # Verify build directory doesn't exist initially + build_dir = os.path.join(temp_repo_dir, "build") + assert not os.path.exists(build_dir) + + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + builder.build(temp_repo_dir, "go-app", artifact) + + # Build directory should be created + # Note: In the actual code, this is done via os.makedirs("build", exist_ok=True) + # The mock prevents actual directory creation, so we just verify the call was made + mock_run.assert_called_once() + + @patch('lib.checksum.generate_checksum') + def test_publish_success(self, mock_checksum, temp_repo_dir, mocker): + """Test successful artifact publishing.""" + # Setup + artifact_path = os.path.join(temp_repo_dir, "go-app_1.0.0_s390x") + with open(artifact_path, "w") as f: + f.write("fake binary") + + mock_checksum.return_value = "def456" + mock_run = mocker.patch('subprocess.run') + + # Publish + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "go-app", artifact) + + # Assertions + mock_checksum.assert_called_once_with(artifact_path) + mock_run.assert_called_once() + + # Verify gh release create command + gh_call = mock_run.call_args + assert "gh" in gh_call[0][0] + assert "release" in gh_call[0][0] + assert "create" in gh_call[0][0] + assert "v1.0.0" in gh_call[0][0] + assert "--title" in gh_call[0][0] + assert "Version 1.0.0" in gh_call[0][0] + assert "--generate-notes" in gh_call[0][0] + assert artifact_path in gh_call[0][0] + assert f"{artifact_path}.sha256" in gh_call[0][0] + + @patch('lib.checksum.generate_checksum') + def test_publish_default_version(self, mock_checksum, temp_repo_dir, mocker): + """Test publish with default version.""" + artifact_path = os.path.join(temp_repo_dir, "go-app_1.0_s390x") + with open(artifact_path, "w") as f: + f.write("fake binary") + + mock_checksum.return_value = "abc123" + mock_run = mocker.patch('subprocess.run') + + builder = GoBinaryBuilder() + artifact = {} # No version + builder.publish(artifact_path, "go-app", artifact) + + # Should use default version 1.0 + gh_call = mock_run.call_args + assert "v1.0" in gh_call[0][0] + + @patch('lib.checksum.generate_checksum') + def test_publish_subprocess_error(self, mock_checksum, temp_repo_dir, mocker): + """Test publish handles subprocess errors gracefully.""" + artifact_path = os.path.join(temp_repo_dir, "go-app_1.0.0_s390x") + with open(artifact_path, "w") as f: + f.write("fake binary") + + mock_checksum.return_value = "abc123" + + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError( + 1, 'gh', stderr=b'GitHub error' + )) + + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(subprocess.CalledProcessError): + builder.publish(artifact_path, "go-app", artifact) + + @patch('lib.checksum.generate_checksum') + def test_publish_working_directory(self, mock_checksum, temp_repo_dir, mocker): + """Test that publish runs in the correct working directory.""" + artifact_path = os.path.join(temp_repo_dir, "build", "go-app_1.0.0_s390x") + os.makedirs(os.path.dirname(artifact_path), exist_ok=True) + with open(artifact_path, "w") as f: + f.write("fake binary") + + mock_checksum.return_value = "xyz789" + mock_run = mocker.patch('subprocess.run') + + builder = GoBinaryBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "go-app", artifact) + + # Verify cwd parameter + gh_call = mock_run.call_args + assert gh_call[1]['cwd'] == os.path.dirname(artifact_path) diff --git a/tests/test_java_binary_builder.py b/tests/test_java_binary_builder.py new file mode 100644 index 0000000..086cc9b --- /dev/null +++ b/tests/test_java_binary_builder.py @@ -0,0 +1,274 @@ +# Copyright Contributors to the Mainframe Software Hub for Linux Project. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import os +from unittest.mock import MagicMock, call, patch +from builders.binary.java_binary_builder import JavaBinaryBuilder, detect_build_system, BUILD_SYSTEMS + + +class TestDetectBuildSystem: + """Test build system detection for Maven and Gradle.""" + + def test_detect_maven(self, temp_repo_dir): + """Test detection of Maven build system.""" + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + system, config = detect_build_system(temp_repo_dir) + + assert system == "maven" + assert config == BUILD_SYSTEMS["maven"] + + def test_detect_gradle_groovy(self, temp_repo_dir): + """Test detection of Gradle (Groovy) build system.""" + gradle_path = os.path.join(temp_repo_dir, "build.gradle") + with open(gradle_path, "w") as f: + f.write("plugins { id 'java' }") + + system, config = detect_build_system(temp_repo_dir) + + assert system == "gradle" + assert config == BUILD_SYSTEMS["gradle"] + + def test_detect_gradle_kotlin(self, temp_repo_dir): + """Test detection of Gradle (Kotlin) build system.""" + gradle_kts_path = os.path.join(temp_repo_dir, "build.gradle.kts") + with open(gradle_kts_path, "w") as f: + f.write("plugins { kotlin(\"jvm\") }") + + system, config = detect_build_system(temp_repo_dir) + + assert system == "gradle" + assert config == BUILD_SYSTEMS["gradle"] + + def test_no_build_system_found(self, temp_repo_dir): + """Test when no build system is detected.""" + system, config = detect_build_system(temp_repo_dir) + + assert system is None + assert config is None + + +class TestJavaBinaryBuilder: + """Test JavaBinaryBuilder build and publish methods.""" + + def test_build_maven_success(self, temp_repo_dir, mocker): + """Test successful Maven build.""" + # Setup + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + target_dir = os.path.join(temp_repo_dir, "target") + os.makedirs(target_dir, exist_ok=True) + + jar_file = os.path.join(target_dir, "app-1.0.0.jar") + with open(jar_file, "w") as f: + f.write("fake jar content") + + # Mock subprocess.run + mock_run = mocker.patch('subprocess.run') + + # Mock os.listdir to return JAR files + mocker.patch('os.listdir', return_value=["app-1.0.0.jar"]) + + # Build + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + result_path = builder.build(temp_repo_dir, "test-app", artifact) + + # Assertions + assert result_path == os.path.join(temp_repo_dir, "build", "test-app_1.0.0_s390x.jar") + assert mock_run.call_count == 2 # docker run + cp + + # Verify Docker command + docker_call = mock_run.call_args_list[0] + assert "docker" in docker_call[0][0] + assert "run" in docker_call[0][0] + assert "maven:3.9-eclipse-temurin-17" in docker_call[0][0] + assert "mvn" in docker_call[0][0] + assert "-DskipTests" in docker_call[0][0] + + def test_build_gradle_success(self, temp_repo_dir, mocker): + """Test successful Gradle build.""" + # Setup + gradle_path = os.path.join(temp_repo_dir, "build.gradle") + with open(gradle_path, "w") as f: + f.write("plugins { id 'java' }") + + build_dir = os.path.join(temp_repo_dir, "build", "libs") + os.makedirs(build_dir, exist_ok=True) + + jar_file = os.path.join(build_dir, "app-1.0.0.jar") + with open(jar_file, "w") as f: + f.write("fake jar content") + + # Mock subprocess.run + mock_run = mocker.patch('subprocess.run') + + # Mock os.listdir + mocker.patch('os.listdir', return_value=["app-1.0.0.jar"]) + + # Build + builder = JavaBinaryBuilder() + artifact = {"version": "2.0.0"} + result_path = builder.build(temp_repo_dir, "gradle-app", artifact) + + # Assertions + assert result_path == os.path.join(temp_repo_dir, "build", "gradle-app_2.0.0_s390x.jar") + assert mock_run.call_count == 2 + + # Verify Docker command uses Gradle + docker_call = mock_run.call_args_list[0] + assert "gradle:8.7-jdk17" in docker_call[0][0] + assert "gradle" in docker_call[0][0] + + def test_build_custom_docker_image(self, temp_repo_dir, mocker): + """Test build with custom Docker image.""" + # Setup + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + target_dir = os.path.join(temp_repo_dir, "target") + os.makedirs(target_dir, exist_ok=True) + jar_file = os.path.join(target_dir, "app.jar") + with open(jar_file, "w") as f: + f.write("fake jar") + + mock_run = mocker.patch('subprocess.run') + mocker.patch('os.listdir', return_value=["app.jar"]) + + # Build with custom image + builder = JavaBinaryBuilder() + artifact = { + "version": "1.0.0", + "docker_image": "maven:3.9-eclipse-temurin-21" + } + builder.build(temp_repo_dir, "custom-app", artifact) + + # Verify custom Docker image is used + docker_call = mock_run.call_args_list[0] + assert "maven:3.9-eclipse-temurin-21" in docker_call[0][0] + + def test_build_no_build_system_found(self, temp_repo_dir): + """Test build fails when no build system is found.""" + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(RuntimeError, match="No supported Java build file found"): + builder.build(temp_repo_dir, "no-build", artifact) + + def test_build_no_jar_found(self, temp_repo_dir, mocker): + """Test build fails when no JAR file is produced.""" + # Setup Maven project without JAR output + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + target_dir = os.path.join(temp_repo_dir, "target") + os.makedirs(target_dir, exist_ok=True) + + mock_run = mocker.patch('subprocess.run') + mocker.patch('os.listdir', return_value=[]) # No JARs + + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(RuntimeError, match="No runnable JAR found"): + builder.build(temp_repo_dir, "no-jar", artifact) + + def test_build_filters_sources_and_javadoc_jars(self, temp_repo_dir, mocker): + """Test that sources and javadoc JARs are filtered out.""" + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + target_dir = os.path.join(temp_repo_dir, "target") + os.makedirs(target_dir, exist_ok=True) + + # Create multiple JARs + for jar_name in ["app-1.0.0.jar", "app-1.0.0-sources.jar", "app-1.0.0-javadoc.jar"]: + with open(os.path.join(target_dir, jar_name), "w") as f: + f.write("fake jar") + + mock_run = mocker.patch('subprocess.run') + mocker.patch('os.listdir', return_value=[ + "app-1.0.0.jar", + "app-1.0.0-sources.jar", + "app-1.0.0-javadoc.jar" + ]) + + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + builder.build(temp_repo_dir, "multi-jar", artifact) + + # Verify cp command uses main JAR, not sources/javadoc + cp_call = mock_run.call_args_list[1] + assert "app-1.0.0.jar" in cp_call[0][0][1] + assert "sources" not in cp_call[0][0][1] + assert "javadoc" not in cp_call[0][0][1] + + def test_build_subprocess_error(self, temp_repo_dir, mocker): + """Test build handles subprocess errors gracefully.""" + pom_path = os.path.join(temp_repo_dir, "pom.xml") + with open(pom_path, "w") as f: + f.write("") + + # Mock subprocess to raise error + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError(1, 'docker')) + + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(subprocess.CalledProcessError): + builder.build(temp_repo_dir, "error-app", artifact) + + @patch('lib.checksum.generate_checksum') + def test_publish_success(self, mock_checksum, temp_repo_dir, mocker): + """Test successful artifact publishing.""" + # Setup + artifact_path = os.path.join(temp_repo_dir, "test-app_1.0.0_s390x.jar") + with open(artifact_path, "w") as f: + f.write("fake jar") + + mock_checksum.return_value = "abc123" + mock_run = mocker.patch('subprocess.run') + + # Publish + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + builder.publish(artifact_path, "test-app", artifact) + + # Assertions + mock_checksum.assert_called_once_with(artifact_path) + mock_run.assert_called_once() + + # Verify gh release create command + gh_call = mock_run.call_args + assert "gh" in gh_call[0][0] + assert "release" in gh_call[0][0] + assert "create" in gh_call[0][0] + assert "v1.0.0" in gh_call[0][0] + + @patch('lib.checksum.generate_checksum') + def test_publish_subprocess_error(self, mock_checksum, temp_repo_dir, mocker): + """Test publish handles errors gracefully.""" + artifact_path = os.path.join(temp_repo_dir, "test-app_1.0.0_s390x.jar") + with open(artifact_path, "w") as f: + f.write("fake jar") + + mock_checksum.return_value = "abc123" + + # Mock subprocess to raise error + import subprocess + mock_run = mocker.patch('subprocess.run', side_effect=subprocess.CalledProcessError(1, 'gh')) + + builder = JavaBinaryBuilder() + artifact = {"version": "1.0.0"} + + with pytest.raises(subprocess.CalledProcessError): + builder.publish(artifact_path, "test-app", artifact) From cb7f03ba0c4cc08bf4fcb6bd5d4dafcb98371212 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Fri, 20 Mar 2026 22:41:16 +0530 Subject: [PATCH 4/5] feat: added github workflow file for automated test runs on CI Signed-off-by: rohansen856 --- .github/workflows/tests.yml | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..22bda7d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,79 @@ +# Copyright Contributors to the Mainframe Software Hub for Linux Project. +# SPDX-License-Identifier: Apache-2.0 + +name: Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + name: Run Unit Tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + pytest -v --cov=builders --cov-report=xml --cov-report=term + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: matrix.python-version == '3.11' + with: + file: ./coverage.xml + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + + lint: + name: Code Quality + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 black + + - name: Check code formatting with black + run: | + black --check --diff builders/ tests/ + continue-on-error: true + + - name: Lint with flake8 + run: | + # Stop the build if there are Python syntax errors or undefined names + flake8 builders/ tests/ --count --select=E9,F63,F7,F82 --show-source --statistics + # Exit-zero treats all errors as warnings + flake8 builders/ tests/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + continue-on-error: true From feb1f222d2206a6570a03c14b755f07b2023f95c Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Fri, 20 Mar 2026 22:55:43 +0530 Subject: [PATCH 5/5] chore: removed overhead comments Signed-off-by: rohansen856 --- .github/workflows/tests.yml | 3 --- tests/conftest.py | 3 --- tests/test_go_binary_builder.py | 3 --- tests/test_java_binary_builder.py | 3 --- tests/test_script_builder.py | 3 --- 5 files changed, 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22bda7d..c90adab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,3 @@ -# Copyright Contributors to the Mainframe Software Hub for Linux Project. -# SPDX-License-Identifier: Apache-2.0 - name: Tests on: diff --git a/tests/conftest.py b/tests/conftest.py index 286727a..3329e93 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,3 @@ -# Copyright Contributors to the Mainframe Software Hub for Linux Project. -# SPDX-License-Identifier: Apache-2.0 - import pytest import os import tempfile diff --git a/tests/test_go_binary_builder.py b/tests/test_go_binary_builder.py index 9ccd84e..4cc571a 100644 --- a/tests/test_go_binary_builder.py +++ b/tests/test_go_binary_builder.py @@ -1,6 +1,3 @@ -# Copyright Contributors to the Mainframe Software Hub for Linux Project. -# SPDX-License-Identifier: Apache-2.0 - import pytest import os from unittest.mock import patch diff --git a/tests/test_java_binary_builder.py b/tests/test_java_binary_builder.py index 086cc9b..18e54b0 100644 --- a/tests/test_java_binary_builder.py +++ b/tests/test_java_binary_builder.py @@ -1,6 +1,3 @@ -# Copyright Contributors to the Mainframe Software Hub for Linux Project. -# SPDX-License-Identifier: Apache-2.0 - import pytest import os from unittest.mock import MagicMock, call, patch diff --git a/tests/test_script_builder.py b/tests/test_script_builder.py index bb9b11f..fc9523b 100644 --- a/tests/test_script_builder.py +++ b/tests/test_script_builder.py @@ -1,6 +1,3 @@ -# Copyright Contributors to the Mainframe Software Hub for Linux Project. -# SPDX-License-Identifier: Apache-2.0 - import pytest import os from unittest.mock import patch, MagicMock